In our codebase we have an array-like datastructure class that allocates memory with sun.misc.Unsafe
so it can accept a long
as a size & index parameter. This class also uses the sun.misc.Unsafe
function compareAndSwapLong
to perform compare-and-swap (CAS) operations against elements of the array. This class produces un-silenceable warnings when compiled and per JEP 471 the functionality in sun.misc.Unsafe
is now deprecated, so we are looking to update it to use Java's newer unsafe APIs.
Java's VarHandle
class was introduced in JEP 193/Java 9 and is touted as a safe way to perform low-overhead CAS operations. However, it only appears to operate on existing safe datastructures; for example, to CAS an element of an array the docs show this example:
String[] sa = ...
VarHandle avh = MethodHandles.arrayElementVarHandle(String[].class);
boolean r = avh.compareAndSet(sa, 10, "expected", "new");
the docs then specify that the compareAndSet
call accepts an int
in the index position. Looking through the VarHandle
APIs, nothing jumps out as obviously exposing a CAS operation against memory allocated using either the old sun.misc.Unsafe
method or the new SegmentAllocator
interface introduced in JEP 454/Java 22. Where is this functionality exposed, if anywhere?
Use MemoryLayout::arrayElementVarHandle(PathElement...)
to create a VarHandle
that can access a segment like an array with long
indices.
Creates a var handle that accesses adjacent elements in a memory segment at offsets selected by the given layout path, where the accessed elements have this layout, and where the initial layout in the path is this layout. The returned var handle has the following characteristics:
- its type is derived from the carrier of the selected value layout;
- it has a leading parameter of type
MemorySegment
representing the accessed segment- a following
long
parameter, corresponding to the base offset, denoted asB
;- a following
long
parameter, corresponding to the array index, denoted asI0
. The array index is used to scale the accessed offset by this layout size;- it has zero or more trailing access coordinates of type
long
, one for each open path element in the provided layout path, denoted asI1, I2, ... In
, respectively. The order of these access coordinates corresponds to the order in which the open path elements occur in the provided layout path.[...]
For example:
import java.lang.foreign.Arena;
import java.lang.foreign.ValueLayout;
import java.lang.invoke.MethodHandles;
public class Main {
private static final long LENGTH = 10L;
public static void main(String[] args) {
try (var arena = Arena.ofConfined()) {
var segment = arena.allocate(ValueLayout.JAVA_INT, LENGTH);
var handle = ValueLayout.JAVA_INT.arrayElementVarHandle();
/*
* This binds the MemorySegment (first argument) and offset (second argument) to
* 'segment' and '0L', respectively. This makes it so the MemorySegment and offset
* don't have to be passed every time a method is invoked on the VarHandle. For
* instance, this:
*
* handle.set(segment, 0L, 5L, 42)
*
* Can now just be:
*
* handle.set(5L, 42)
*
* Note if you want to use the VarHandle with multiple MemorySegment arrays, then do
* not bind the MemorySegment argument. You can still bind the offset argument with:
*
* MethodHandles.insertCoordinates(handle, 1, 0L)
*
* The MemorySegment is bound in this example in an attempt to make it more readable.
*/
handle = MethodHandles.insertCoordinates(handle, 0, segment, 0L);
// populate array so not all elements are 0
for (long i = 0; i < LENGTH; i++) {
handle.set(i, (int) i * 2);
}
// print array
for (long i = 0; i < LENGTH; i++) {
int value = (int) handle.get(i);
System.out.printf("[%d] = %d%n", i, value);
}
// demonstrate using compareAndSet
System.out.println();
System.out.println("cas(3, 9, 117): " + handle.compareAndSet(3L, 9, 117)); // will fail
System.out.println("cas(5, 10, 42): " + handle.compareAndSet(5L, 10, 42)); // will succeed
System.out.println();
// print array to show state after compareAndSet
for (long i = 0; i < LENGTH; i++) {
int value = (int) handle.get(i);
System.out.printf("[%d] = %d%n", i, value);
}
}
}
}
Note: In real code you'll probably want to store the VarHandle
in a static final field to improve performance. Which means binding a particular memory segment as the first argument is likely not an option.
Output:
[0] = 0
[1] = 2
[2] = 4
[3] = 6
[4] = 8
[5] = 10
[6] = 12
[7] = 14
[8] = 16
[9] = 18
cas(3, 9, 117): false
cas(5, 10, 42): true
[0] = 0
[1] = 2
[2] = 4
[3] = 6
[4] = 8
[5] = 42
[6] = 12
[7] = 14
[8] = 16
[9] = 18
However, note the access mode restrictions:
Access mode restrictions
A var handle returned by
varHandle(PathElement...)
orValueLayout.varHandle()
features certain access characteristics, which are derived from the selected layoutL
:
- A carrier type
T
, derived fromL.carrier()
- An alignment constraint
A
, derived fromL.byteAlignment()
- An access size
S
, derived fromL.byteSize()
Depending on the above characteristics, the returned var handle might feature certain access mode restrictions. We say that a var handle is aligned if its alignment constraint
A
is compatible with the access sizeS
, that is ifA >= S
. An aligned var handle is guaranteed to support the following access modes:
- read write access modes for all
T
. On 32-bit platforms, access modesget
andset
forlong
,double
andMemorySegment
are supported but might lead to word tearing, as described in Section 17.7. of The Java Language Specification.- atomic update access modes for
int
,long
,float
,double
andMemorySegment
. (Future major platform releases of the JDK may support additional types for certain currently unsupported access modes.)- numeric atomic update access modes for
int
,long
andMemorySegment
. (Future major platform releases of the JDK may support additional numeric types for certain currently unsupported access modes.)- bitwise atomic update access modes for
int
,long
andMemorySegment
. (Future major platform releases of the JDK may support additional numeric types for certain currently unsupported access modes.)If
T
isfloat
,double
orMemorySegment
then atomic update access modes compare values using their bitwise representation (seeFloat.floatToRawIntBits(float)
,Double.doubleToRawLongBits(double)
andMemorySegment.address()
, respectively).Alternatively, a var handle is unaligned if its alignment constraint
A
is incompatible with the access sizeS
, that is, ifA < S
. An unaligned var handle only supports theget
andset
access modes. All other access modes will result inUnsupportedOperationException
being thrown. Moreover, while supported, access modesget
andset
might lead to word tearing.
The documentation of VarHandle
explains which methods belong to each of the five access "groups" (i.e., "read", "write", "atomic update", "numeric atomic update", and "bitwise atomic update").
This means methods like compareAndSet
will throw an UnsupportedOperationException
(at least in Java 22) for value layouts such as JAVA_BYTE
. Which means, as far as I can tell, you won't be able to create a direct analog of byte[]
/ ByteBuffer
that uses long
indices.