如何在Java中声明数组元素为volatile?

38

在Java中有没有一种方法可以声明数组元素为 volatile?例如:

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

声明了数组引用volatile,但是数组元素(例如a[1])仍然不是volatile。因此我正在寻找类似于以下内容:

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

但事实并非如此。这种情况有可能吗?


@Kanagavelu Sugumar:AtomicReference是一个包装了volatile的类,还带有一些额外的方法(例如getAndSet等)。 - Joonas Pulakka
是的,您可以将数组设置为volatile。请参考此链接 - http://javarevisited.blogspot.in/2015/10/133-java-interview-questions-answers-from-last-5-years.html - reactdontact
4个回答

33

使用 AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

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

详见:


我想知道为什么有特定的AtomicArrays用于int和long,但其他原始类型却没有...当然,可以通过在AtomicReferenceArray中使用它们的包装器来模拟其余的原始类型。 - Joonas Pulakka
我认为AtomicIntegerArray和AtomicLongArray分别针对整数和长整数进行了优化。 - uthark
4
对于其他原始类型,你也可以使用例如Float.floatToIntBits(float)将它们转换为int或long。这可以避免在使用AtomicReferenceArray时需要装箱的情况。 - Didier L

7

实际上你是可以的,但需要付出额外的努力。 - uthark
从技术上讲,这仍然没有使元素成为volatile,但数组操作是volatile的。由于对于此情况而言int是一个原始类型,因此它基本上具有相同的行为,但是这不能轻易地扩展到非原始数组。 - Marcus

6

另一种方法是使用JDK 9+的VarHandle类。正如您可以在AtomicxxxArray类的源代码中看到的那样,例如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
然后,您可以使用setxxx(array, index, value)getxxx(array, index)方法访问它,其中arrayyourArrayClass类型,indexint类型,value是数组元素类型的值(yourArrayClass.getComponentType())。
请注意,如果例如yourArrayClass == byte[].class,但您将42作为value输入,则会出现错误,因为42int而不是byte,访问方法的参数是可变参数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开始,由于“未解决的循环启动依赖项”,Unsafe仍然在AtomicInteger中使用。


如果您想了解不同的获取和设置方法,可以查看使用JDK 9内存顺序模式(我必须说,我对此并不是很熟悉(还?))。

请注意,截至今天,您无法在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

(现在应该已经修复)


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;

单元格也使内容变得不稳定。在预分配单元格之后,我只将新数组分配给易失性的数组...这里存在单元格额外内存的权衡,但是它是可以管理的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接