简体   繁体   中英

How to declare array elements volatile in Java?

Is there a way to declare array elements volatile in Java? Ie

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

declares the array reference volatile , but the array elements (eg a[1] ) are still not volatile. So I'm looking for something like

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

but it doesn't work that way. Is it possible at all?

Use AtomicIntegerArray or AtomicLongArray or AtomicReferenceArray

The AtomicIntegerArray class implements an int array whose individual fields can be accessed with volatile semantics, via the class's get() and set() methods. Calling arr.set(x, y) from one thread will then guarantee that another thread calling arr.get(x) will read the value y (until another value is read to position x).

See:

No, you can't make array elements volatile. See also http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html .

Another way to do this is using the JDK 9+ VarHandle class. As you can see in the source code of the Atomic xxx Array classes like AtomicIntegerArray , these classes also use VarHandle since JDK 9:

//[...]

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);
}

//[...]

You first create a VarHandle like this:

MethodHandles.arrayElementVarHandle(yourArrayClass)

For example, you can enter byte[].class here to implement the missing AtomicByteArray yourself.

And then you can access it using the set xxx (array, index, value) and get xxx (array, index) methods, where array is of type yourArrayClass , index is of type int , value is of the type of an element in your array ( yourArrayClass.getComponentType() ).

Note that if, for example, yourArrayClass == byte[].class but you enter 42 as value , you get an error because 42 is an int instead of a byte and the parameters of the access methods are vararg Object... parameters:

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

(The second signature is the one that you used, the first one is the one that you should have used.)


Note that in JDK 8 and below sun.misc.Unsafe was used to implement the atomic classes like 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);
}

//[...]

Using Unsafe is still an option (although I think it's a bit tricky to obtain an instance), but it is discouraged because you have to check array bounds yourself and it might segfault the Java process if you make a mistake, while VarHandle does bounds checks for you and throws a Java exception if the given index is out of bounds (but that may come with a performance cost). Besides that, Unsafe is not officially supported and might be removed at any time.

But as of JDK 10 Unsafe is still used in AtomicInteger because of 'unresolved cyclic startup dependencies' .


If you want to know more about the different get and set methods available, take a look at Using JDK 9 Memory Order Modes (I have to say that I'm not an expert in this at all (yet?)).


Note that as of today you cannot use VarHandle in Kotlin, because it wraps the vararg Object... parameters of the get and set methods in an Object[] , see bug KT-26165 :

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

How about this:

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;

the cells make the content volatile too. also I assign the new array to the volatile one only after pre allocating the cells... there's the trade off of the Cell extra memory, but it's manageable

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM