如何用Java声明数组元素volatile?

问题描述 投票:33回答:4

有没有办法在Java中声明数组元素volatile?即

volatile int[] a = new int[10];

声明数组引用volatile,但数组元素(例如a[1])仍然不是volatile。所以我正在寻找类似的东西

volatile int[] a = new volatile int[10];

但它不会那样工作。有可能吗?

java arrays concurrency volatile
4个回答
28
投票

使用AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

AtomicIntegerArray类实现了一个int数组,通过类的get()set()方法,可以使用volatile语义访问各个字段。从一个线程调用arr.set(x, y)将保证调用arr.get(x)的另一个线程将读取值y(直到另一个值被读取到位置x)。

看到:


6
投票

不,你不能使数组元素易变。另见http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html


3
投票

另一种方法是使用JDK 9+ VarHandle类。正如您在AtomicxwixixxxArray类的源代码中所看到的那样,这些类也从JDK 9开始使用AtomicIntegerArray

VarHandle

你首先像这样创建一个//[...] private static final VarHandle AA = MethodHandles.arrayElementVarHandle(int[].class); private final int[] array; //[...] /** * Returns the current value of the element at index {@code i}, * with memory effects as specified by {@link VarHandle#getVolatile}. * * @param i the index * @return the current value */ public final int get(int i) { return (int)AA.getVolatile(array, i); } /** * Sets the element at index {@code i} to {@code newValue}, * with memory effects as specified by {@link VarHandle#setVolatile}. * * @param i the index * @param newValue the new value */ public final void set(int i, int newValue) { AA.setVolatile(array, i, newValue); } //[...]

VarHandle

例如,你可以在这里输入MethodHandles.arrayElementVarHandle(yourArrayClass) 来自己实现缺少的byte[].class

然后你可以使用AtomicByteArrayxxxset(array, index, value)xxxget方法访问它,其中(array, index)的类型为arrayyourArrayClass的类型为indexint是你的数组中元素的类型(value)。

请注意,如果,例如,yourArrayClass.getComponentType(),但你输入yourArrayClass == byte[].class作为42,你得到一个错误,因为value是一个42而不是int,访问方法的参数是vararg byte参数:

Object...

(第二个签名是您使用的签名,第一个签名是您应该使用的签名。)


请注意,在JDK 8及以下版本中,java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(VarHandle,byte[],int,byte)void to (VarHandle,byte[],int,int)void 用于实现像sun.misc.Unsafe这样的原子类:

AtomicIntegerArray

使用//[...] private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int base = unsafe.arrayBaseOffset(int[].class); private static final int shift; private final int[] array; static { int scale = unsafe.arrayIndexScale(int[].class); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); shift = 31 - Integer.numberOfLeadingZeros(scale); } private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } private static long byteOffset(int i) { return ((long) i << shift) + base; } //[...] /** * Gets the current value at position {@code i}. * * @param i the index * @return the current value */ public final int get(int i) { return getRaw(checkedByteOffset(i)); } private int getRaw(long offset) { return unsafe.getIntVolatile(array, offset); } /** * Sets the element at position {@code i} to the given value. * * @param i the index * @param newValue the new value */ public final void set(int i, int newValue) { unsafe.putIntVolatile(array, checkedByteOffset(i), newValue); } //[...] 仍然是一个选项(虽然我认为获取实例有点棘手),但是不鼓励它因为你必须自己检查数组边界,如果你犯了错误它可能会破坏Java进程,而Unsafe会进行边界检查对于你,如果给定的索引超出范围,则抛出Java异常(但这可能带来性能成本)。除此之外,VarHandle不受官方支持,可能随时被删除。

但是从JDK 10开始,由于'未解决的循环启动依赖性',Unsafe仍在Unsafe中使用。


如果你想了解更多关于不同的get和set方法的信息,请看看AtomicInteger(我必须说我根本就不是这方面的专家(还是?))。


请注意,截至今天你不能在Kotlin中使用Using JDK 9 Memory Order Modes,因为它包含了VarHandle中get和set方法的vararg Object...参数,请参阅Object[]

bug KT-26165

0
投票

这个怎么样:

java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(VarHandle,byte[],int,byte)void to (VarHandle,Object[])void

细胞也使内容易变。我也只是在预先分配了单元格之后才将新数组分配给volatile数组...还有Cell额外内存的权衡,但它是可管理的

© www.soinside.com 2019 - 2024. All rights reserved.