在Java中有没有一种方法可以声明数组元素为 volatile
?例如:
volatile int[] a = new int[10];
声明了数组引用volatile
,但是数组元素(例如a[1]
)仍然不是volatile
。因此我正在寻找类似于以下内容:
volatile int[] a = new volatile int[10];
但事实并非如此。这种情况有可能吗?
在Java中有没有一种方法可以声明数组元素为 volatile
?例如:
volatile int[] a = new int[10];
声明了数组引用volatile
,但是数组元素(例如a[1]
)仍然不是volatile
。因此我正在寻找类似于以下内容:
volatile int[] a = new volatile int[10];
但事实并非如此。这种情况有可能吗?
使用 AtomicIntegerArray
或 AtomicLongArray
或 AtomicReferenceArray
AtomicIntegerArray
类实现了一个 int 数组,可以通过类的 get()
和 set()
方法以 volatile 的方式访问其各个字段。从一个线程调用 arr.set(x, y)
将确保另一个调用 arr.get(x)
的线程将读取值 y (直到另一个值被读取到位置 x)。
详见:
AtomicArrays
用于int和long,但其他原始类型却没有...当然,可以通过在AtomicReferenceArray
中使用它们的包装器来模拟其余的原始类型。 - Joonas PulakkaFloat.floatToIntBits(float)
将它们转换为int或long。这可以避免在使用AtomicReferenceArray
时需要装箱的情况。 - Didier L不,你不能将数组元素设为volatile。请参阅http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html。
int
是一个原始类型,因此它基本上具有相同的行为,但是这不能轻易地扩展到非原始数组。 - Marcus另一种方法是使用JDK 9+的VarHandle
类。正如您可以在Atomic
xxxArray
类的源代码中看到的那样,例如AtomicIntegerArray
,自JDK 9以来这些类也使用了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
,以自己实现缺失的AtomicByteArray
。set
xxx(array, index, value)
和get
xxx(array, index)
方法访问它,其中array
是yourArrayClass
类型,index
是int
类型,value
是数组元素类型的值(yourArrayClass.getComponentType()
)。yourArrayClass == byte[].class
,但您将42
作为value
输入,则会出现错误,因为42
是int
而不是byte
,访问方法的参数是可变参数Object...
参数。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);
}
//[...]
Unsafe
仍然是一种选择(尽管我认为获得实例有点棘手),但是不建议使用,因为您必须自己检查数组边界,如果犯错误可能会导致Java进程崩溃,而VarHandle
会为您执行边界检查,并在给定索引超出范围时抛出Java异常(但这可能会带来性能成本)。此外,Unsafe
没有得到官方支持,随时可能被删除。
但是从JDK 10开始,由于“未解决的循环启动依赖项”,Unsafe
仍然在AtomicInteger
中使用。
请注意,截至今天,您无法在Kotlin中使用VarHandle
,因为它将get和set方法的vararg Object...
参数包装在Object[]
中,参见bug KT-26165:
java.lang.invoke.WrongMethodTypeException: cannot convert MethodHandle(VarHandle,byte[],int,byte)void to (VarHandle,Object[])void
(现在应该已经修复)
static class Cell<T> {
volatile T elem;
}
private Cell<T>[] alloc(int size){
Cell<T>[] cells = (Cell<T>[]) (new Cell[size]);
return cells;
}
volatile Cell<T>[] arr;
Cell<T>[] newarr = alloc(16);
for (int i = 0; i < newarr.length; i++) {
newarr[i] = new Cell<>();
}
arr = newarr;
单元格也使内容变得不稳定。在预分配单元格之后,我只将新数组分配给易失性的数组...这里存在单元格额外内存的权衡,但是它是可以管理的。
volatile
的类,还带有一些额外的方法(例如getAndSet等)。 - Joonas Pulakka