3 Acumuladores Atómicos¶
Acumuladores Atómicos¶
Si tenemos muchos hilos actualizando simultáneamente una variable atómica, el número de fallos y reintentos que produce la técnica compare and swap es muy alto. Con objeto de minimizar el tiempo de ejecución asociado a dichos reintentos, Java 8 introdujo clases específicas para crear variables contadoras o acumuladoras thread-safe que internamente emplean técnicas de atomicidad distintas a las de las variables atómicas y que pueden proporcionar mejoras en el rendimiento respecto a ellas en determinadas situaciones como la descrita anteriormente.
Acumulador atómico
Variable que no almacena un único valor que se va modificando, sino una lista de celdas de acumulación atómicas
Estas clases no almacenan un único valor que va modificando conforme se actualiza el valor del acumulador, sino que almacenan una lista de celdas de acumulación atómicas en la que almacenan los valores acumulados.
Al intentar acumular un valor en el acumulador, se busca alguna celda de acumulación que no esté siendo usada en ese momento y se realiza en ella la acumulación.
Cuando se quiere obtener el valor resultante se ejecuta una función de acumulación sobre los elementos de la lista de celdas para obtener el resultado de la acumulación.
Las clases incorporadas son:
DoubleAccumulator
: Corresponde a uno o más valores que al aplicarles una determinada función conforman un único valor de tipoDouble
. La función a utilizar debe implementar la interfaz funcionalDoubleBinaryOperator
y ser pasado al constructor del objetoDoubleAccumulator
, que también recibirá el valor inicial (llamado identity, identidad).DoubleAdder
: Corresponde a uno o más valores que sumados a partir del valor 0 conforman un único valor de tipoDouble
. Es caso especial deDoubleAccumulator
donde la función de acumulación corresponde a la suma y el valor inicial es 0, es decir, equivalente anew DoubleAccumulator((x, y) -> x + y, 0.0)
LongAccumulator
: Similar aDoubleAccumulator
pero para tiposLong
.LongAdder
: Similar aDoubleAdder
pero para tiposLong
.
Para acumular un nuevo valor a un objeto de alguna de estas clases usaremos el método accumulate(value)
. Para obtener el valor resultante de la acumulación usaremos el método get()
, en DoubleAccumulator
y LongAccumulator
, o el método sum()
en DoubleAdder
y LongAdder
. También tendremos disponibles métodos tipoValue()
para obtener el valor resultante en un tipo determinado, por ejemplo intValue()
retornará el valor resultante de la acumulación en forma de entero.
Finalmente, mediante el método reset()
podemos resetear el valor del acumulador a la identidad (valor inicial).
Se recomienda el empleo de estas clases respecto a las variables Atomic cuando la frecuencia de operaciones de acumulación sea mucho mayor que el de operaciones de lectura del valor.
Proyecto DoubleAdder¶
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 variable DoubleAdder
.
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: %d€\n",
LocalTime.now().format(dateTimeFormatter), account.getBalance());
// 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: %d€\n",
LocalTime.now().format(dateTimeFormatter), account.getBalance());
}
}
import java.util.concurrent.atomic.DoubleAdder;
public class Account {
private final DoubleAdder balance = new DoubleAdder();
public Account(double initialBalance) {
balance.add(initialBalance);
}
public double getBalance() {
return balance.doubleValue();
}
public void deposit(long amount) {
balance.add(amount);
}
public void debit(long amount) {
balance.add(-amount);
}
}
Si ejecutamos el programa veremos que siempre produce el resultado correcto.