How to declare array elements volatile in Java?
Asked Answered
D

4

38

Is there a way to declare array elements volatile in Java? I.e.

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

declares the array reference volatile, but the array elements (e.g. 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?

Decompress answered 10/2, 2010 at 10:53 Comment(2)
@Kanagavelu Sugumar: AtomicReference is a wrapped volatile, with some extra methods (getAndSet etc.).Decompress
Yes you can make array volatile. Please refer this - javarevisited.blogspot.in/2015/10/…Arius
U
33

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:

Uncontrollable answered 10/2, 2010 at 10:56 Comment(3)
I wonder why there are specific AtomicArrays for int and long, but not for other primitive types... But of course the rest of the primitives could be faked by using their wrappers in an AtomicReferenceArray.Decompress
I think AtomicIntegerArray and AtomicLongArray are optimized to work with integer and long respectively.Uncontrollable
@JoonasPulakka for other primitive types you can also convert them to int or long using e.g. Float.floatToIntBits(float). This avoids the need of boxing when using AtomicReferenceArray.Jelly
L
7

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

Lacey answered 10/2, 2010 at 10:58 Comment(2)
Actually you can, but with additional efforts.Uncontrollable
Technically that's still not making the elements volatile, but the array operations are volatile. Since int for this case is a primitive, it essentially has the same behavior, but this could not be easily extended for non-primitive arrays.Joellenjoelly
G
6

Another way to do this is using the JDK 9+ VarHandle class. As you can see in the source code of the AtomicxxxArray 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 setxxx(array, index, value) and getxxx(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

(should be fixed now)

Gimlet answered 16/8, 2018 at 11:12 Comment(0)
D
0

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

Dulciedulcify answered 6/3, 2019 at 10:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.