Skip to content

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:

public class Account { 

    public double amount; 

    public Account() { 
        this.amount = 0; 
    } 

}

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 de object referenciado por el VarHandle, como si el atributo hubiera sido definido como no volátil (sin la palabra reservada volatile).
  • getVolatile(object): Lee el valor del atributo de object referenciado por el VarHandle, como si el atributo hubiera sido definido como volátil (volatile).

Si queremos acceder para escritura podemos usar:

  • get(object, newValue): Escribe newValue como valor del atributo de object referenciado por el VarHandle, como si el atributo hubiera sido definido como no volátil (sin la palabra reservada volatile).
  • setVolatile(object, newValue): Escribe newValue como valor del atributo de object referenciado por el VarHandle, como si el atributo hubiera sido definido como volátil (volatile).
  • compareAndSet(object, expectedValue, newValue): Escribe newValue como valor del atributo de object referenciado por el VarHandle, como si el atributo hubiera sido definido como volátil (volatile), sólo si el valor actual del atributo corresponde con expectedValue.

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);
    }

}
public class Account {

    public double balance;

    public Account(double initialBalance) {
        balance = initialBalance;
    }

}
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();
        }
    }

}