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

时间:2010-02-10 10:53:34

标签: java arrays concurrency volatile

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

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

声明数组引用 volatile,但数组元素(例如a[1])仍然不易变。所以我正在寻找像

这样的东西
volatile int[] a = new volatile int[10];

但它不起作用。它有可能吗?

4 个答案:

答案 0 :(得分:28)

使用AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

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

请参阅:

答案 1 :(得分:6)

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

答案 2 :(得分:3)

另一种实现方法是使用JDK 9+ VarHandle类。如您在AtomicIntegerArray之类的Atomic xxx Array类的源代码中所看到的,自JDK 9开始,这些类还使用VarHandle: / p>

//[...]

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的类型为yourArrayClassindex的类型为intvalue的类型为数组中元素的类型(yourArrayClass.getComponentType() )。

请注意,例如,如果yourArrayClass == byte[].class却以42的形式输入value,则会出现错误,因为42int而不是a byte和访问方法的参数是vararg Object...参数:

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

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


请注意,在JDK 8和更低版本中,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开始,{<1}}仍在AtomicInteger中使用,因为“未解决的循环启动依赖项”


如果您想了解更多有关可用的不同get和set方法的信息,请查看Using JDK 9 Memory Order Modes(我必须说我还不是专家(还好吗?)。)。 / p>


请注意,到目前为止,您无法在Kotlin中使用Unsafe,因为它会将get和set方法的vararg VarHandle参数包装在Object...中,请参阅bug KT-26165

Object[]

答案 3 :(得分:0)

如何?

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数组...虽然可以牺牲Cell的额外内存,但是它是可管理的