4 VarHandle¶
VarHandle¶
Java 9 incorporó la clase VarHandle
, como una forma de almacenar una referencia tipada (con tipo) a un determinado atributo (estático o no) de una clase o un elemento de array, de manera que se puede acceder al valor de dicho atributo en un determinado objeto a través del VarHandle
. El objetivo de esta clase es proporcionar una funcionalidad de acceso equivalente a la de las variables atómicas en propiedades de objetos (campos, fields) y en elementos de arrays.
VarHandle
Referencia a una variable que permite acceder a ella con distintos métodos de acceso
La ventaja de este acceso referenciado es que podemos usar métodos de nuestro VarHandle
para acceder de diferentes formas al atributo referenciado de un determinado objeto, sin tener que usar ningún mecanismo de sincronización ni usar ningún wrapper al definir el atributo. Así tendremos diferentes modos de acceso: acceso normal de lectura y escritura, acceso volátil de lectura y escritura y, el más interesante, acceso mediante CAS (compare and swap), mediante el cuál podemos ofrecer una acceso thread-safe al valor de dicho atributo en cualquier objeto de la clase correspondiente.
Hasta ahora esta opción de acceso thread-safe sin usar ningún mecanismo de sincronización sólo estaba disponible a través de la clases Atomic, Accumulator o Adder, que actuaban como wrappers de los tipos más básicos. Sin embargo, gracias a VarHandle
podemos extender esta funcionalidad de acceso thread-safe a cualquier tipo de atributo, no sólo a los de tipo primitivo básico.
Por ejemplo, si tenemos definida una clase como la siguiente:
en el siguiente código podemos ver cómo obtener la referencia al atributo amount
de dicha clase y almacenarla en un VarHandle
, para posteriormente acceder de forma thread-safe al valor dicho atributo en un determinado objeto:
public class Incrementer implements Runnable {
private Account account;
public Incrementer(Account account) {
this.account = account;
}
@Override
public void run() {
VarHandle varHandle;
try {
// Se obtiene una referencia al atributo amount de la clase Account, que es
// de tipo double. La referencia es genérica a dicho atributo, y podremos usarla
// sobre cualquier objeto de la clase Account.
varHandle = MethodHandles.lookup().in(Account.class)
.findVarHandle(Account.class, "amount", double.class);
// Se accede de forma thread-safe al atributo amount del objeto Account
// recibido en el contructor, en este caso para incrementar su valor.
for (int i = 0; i < 10000; i++) {
varHandle.getAndAdd(account, 100);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// El atributo no existe en la clase Account o se ha producido un acceso ilegal.
e.printStackTrace();
}
}
}
Warning
Este ejemplo podría haber realizado definiendo el atributo amount
como un DoubleAdder
, pero lo hemos elegido para demostrar la clase VarHandle
por su simplicidad.
Si queremos acceder para lectura al atributo de una variable a través de un VarHandle
tenemos a nuestra disposición una serie de métodos, entre los que destacamos:
get(object)
: Lee el valor del atributo deobject
referenciado por elVarHandle
, como si el atributo hubiera sido definido como no volátil (sin la palabra reservadavolatile
).getVolatile(object)
: Lee el valor del atributo deobject
referenciado por elVarHandle
, como si el atributo hubiera sido definido como volátil (volatile
).
Si queremos acceder para escritura podemos usar:
get(object, newValue)
: EscribenewValue
como valor del atributo deobject
referenciado por elVarHandle
, como si el atributo hubiera sido definido como no volátil (sin la palabra reservadavolatile
).setVolatile(object, newValue)
: EscribenewValue
como valor del atributo deobject
referenciado por elVarHandle
, como si el atributo hubiera sido definido como volátil (volatile
).compareAndSet(object, expectedValue, newValue)
: EscribenewValue
como valor del atributo deobject
referenciado por elVarHandle
, como si el atributo hubiera sido definido como volátil (volatile
), sólo si el valor actual del atributo corresponde conexpectedValue
.
Info
Para saber más visite https://www.baeldung.com/java-variable-handles
Proyecto VarHandle¶
En este proyecto simularemos el funcionamiento de una cuenta bancaria, en la que una empresa realiza abonos desde un hilo y el banco realiza cargos desde otro hilo. El saldo de la cuenta será almacenado usando una propiedad pública double
.
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class Main {
public static void main(String[] args) throws InterruptedException {
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
Account account = new Account(0);
// Show initial balance.
System.out.printf("%s -> Initial balance: %.2f€\n",
LocalTime.now().format(dateTimeFormatter), account.balance);
// Start both saver and consumer threads. Both threads share the same account.
Thread saverThread = new Thread(new AccountSaver(account));
saverThread.start();
Thread consumerThread = new Thread(new AccountConsumer(account));
consumerThread.start();
// Wait for both threads to finish.
saverThread.join();
consumerThread.join();
// Show final balance.
System.out.printf("\n%s -> Final balance: %.2f€\n",
LocalTime.now().format(dateTimeFormatter), account.balance);
}
}
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class AccountSaver implements Runnable {
private final Account account;
public AccountSaver(Account account) {
this.account = account;
}
@Override
public void run() {
VarHandle varHandle;
try {
varHandle = MethodHandles.lookup().in(Account.class)
.findVarHandle(Account.class, "balance", double.class);
for (int i = 0; i < 100000; i++) {
varHandle.getAndAdd(account, 10);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class AccountConsumer implements Runnable {
private final Account account;
public AccountConsumer(Account account) {
this.account = account;
}
@Override
public void run() {
VarHandle varHandle;
try {
varHandle = MethodHandles.lookup().in(Account.class).findVarHandle(Account.class, "balance", double.class);
for (int i = 0; i < 100000; i++) {
varHandle.getAndAdd(account, -5);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
}