How to declare array elements volatile in Java?
No, you can't make array elements volatile. See also http://jeremymanson.blogspot.com/2009/06/volatile-arrays-in-java.html .
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:
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
- java.util.concurrent.atomic Package Summary
Another way to do this is using the JDK 9+ VarHandle
class. As you can see in the source code of the Atomic
xxxArray
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
(should be fixed now)