Atomic* La classe AtomicInteger (par exemple) est simple à utiliser mais...
La classe java.lang.invoke.VarHandle permet
de palier ces problèmes avec une API moins simple...
VarHandleUn VarHandle correspond à un "pointeur" (une handle) sur un
champ volatile ou une case d'un tableau (aussi volatile).
Lookup lookup = MethodHandles.lookup(); VarHandle handle = lookup.findVarHandle( SafeCounter.class, // classe contenant le champ "counter", // nom du champ int.class); // type du champ
Lookup.findVarHandle() permet de créer un VarHandle à
partir d'un objet Lookup correspondant à un contexte de sécurité.
MethodHandles.lookup() permet de demander le contexte de
sécurité à l'endroit de l'appel.
API des VarHandlegetVolatile(object) permet d’accéder à la valeur du champ à partir
d'une référence sur l'objet.setVolatile(object, value) change la valeur du champ.compareAndSet(object, oldValue, newValue) fait un CAS.
class Foo {
volatile int field; // volatile n'est pas obligatoire
// mais c'est mieux !
}
...
var handle = lookup.findVarHandle(Foo.class, "field", int.class);
var foo = new Foo();
handle.setVolatile(foo, 17); // écriture volatile
Attention, get() et set() ne font pas des accès volatiles !
VarHandle sur des cases de tableau
La méthode MethodHandles.arrayElementVarHandle permet d'obtenir
un VarHandle sur les cases d'un tableau.
VarHandle arrayHandle =
MethodHandles.arrayElementVarHandle(String[].class);
...
arrayHandle.setVolatile(array, index, value);
Le premier argument des méthodes du VarHandle est le tableau,
le second est l'index de la case.
VarHandlePour des raisons de performances :
VarHandle dans une constantewithInvokeExactBehavior().
public class SafeCounter {
private volatile int counter;
private static final VarHandle COUNTER_HANDLE;
static {
var lookup = MethodHandles.lookup();
try {
COUNTER_HANDLE = lookup.findVarHandle(SafeCounter.class, "counter", int.class)
.withInvokeExactBehavior();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public int nextValue() {
for (;;) {
int current = this.counter; // lecture volatile
if (COUNTER_HANDLE.compareAndSet(this, current, current + 1)) {
return current;
} // otherwise retry
}
}
}
getAndAddLa méthode getAndAdd() prend en paramètre la valeur de l'incrément :
class SafeCounter {
private volatile int counter;
private static final VarHandle COUNTER_HANDLE;
static {
var lookup = MethodHandles.lookup();
try {
COUNTER_HANDLE = lookup.findVarHandle(SafeCounter.class, "counter", int.class)
.withInvokeExactBehavior();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public int nextValue() {
return (int) COUNTER_HANDLE.getAndAdd(this, 1);
}
public int previousValue() {
return (int) COUNTER_HANDLE.getAndAdd(this, -1);
}
}
On peut remarquer des casts bizarres...
La classe VarHandle n'utilise pas les types paramétrés. Ses méthodes acceptent différents paramètres sans faire de surcharge...
mais par contre,
withInvokeExactBehavior(),pour que ce soit réellement efficace.
public class SafeCounter {
private volatile long counter;
private static final VarHandle COUNTER_HANDLE;
static {
var lookup = MethodHandles.lookup();
try {
COUNTER_HANDLE = lookup.findVarHandle(SafeCounter.class, "counter", long.class)
.withInvokeExactBehavior();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public long nextValue() {
return (long) COUNTER_HANDLE.getAndAdd(this, 1L);
}
}
Quasiment aucune implantation de
structures de données du
package java.util.concurrent n'utilise de
bloc synchronized...
On utilise :
ReentrantLock,volatile et des CAS.Cette classe n'est pas thread-safe.
public class LinkedList<E> {
private record Link<E>(E value, Link<E> next) {}
private Link<E> head;
public void addFirst(E value) {
Objects.requireNonNull(value);
head = new Link<>(value, head);
}
public void forEach(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
for (var current = head; current != null; current = current.next) {
consumer.accept(current.value);
}
}
}
AtomicReference
public class LinkedListThreadSafe<E> {
...
private final AtomicReference<Link<E>> head = new AtomicReference<>();
public void addFirst(E value) {
Objects.requireNonNull(value);
for(;;) {
var oldHead = head.get(); // lecture volatile
var newHead = new Link<>(value, oldHead);
if (head.compareAndSet(oldHead, newHead)) { // lecture/écriture volatile
return;
}
}
}
public void forEach(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
for( var current = head.get(); current!=null; current = current.next ) { // lecture volatile
consumer.accept(current.value);
}
}
}
VarHandle
public class LinkedListVarHandle<E> {
...
private volatile Link<E> head;
private static final VarHandle HEADER_HANDLE;
static {
var lookup = MethodHandles.lookup();
try {
HEADER_HANDLE = lookup.findVarHandle(LinkedListVarHandle.class, "head", Link.class)
.withInvokeExactBehavior();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
public void addFirst(E value) {
Objects.requireNonNull(value);
for (;;) {
var oldHead = head; // lecture volatile
var newHead = new Link<>(value, oldHead);
if (HEADER_HANDLE.compareAndSet(this, oldHead, newHead)) { // lecture/écriture volatile
return;
}
}
}
}