package ar.com.javacuriosities.concurrency.atomic;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
/*
* El paquete java.util.concurrent.atomic.*; contiene muchas clases útiles para realizar operaciones atómicas, una operación atómica una
* operación que se ejecuta de forma total nunca parcial.
*
* La operación "c++" incrementa la variable "c" en uno pero esto consta de tres operaciones
* 1- Lectura del valor actual de "c"
* 2- Incremento del valor
* 3- Asignar el resultado a la variable "c"
*
* Por ende para lograr que esto se maneje de forma atómica debemos usar sincronización o alguna otra técnica, las clases del paquete atomic
* hacen uso de técnicas como CAS (Compare-And-Swap) lo cual evita la sincronización y asegura la atomicidad
*
* CAS Loop:
* for (;;) {
* int current = get();
* int next = current + 1;
* if (compareAndSet(current, next)) {
* return next;
* }
* }
*
* Nota: Es importante notar que a partir de Java 1.8 se modificaron algunas de estas clases para no usar CAS y utilizar XADD (Fetch and Add)
*
* Update Java 1.8:
* En Java 1.8 se agregaron clases como
* - LongAdder: Esta clase es la alternativa a AtomicLong cuando varios thread actualizan un mismo valor, dado que el algoritmo que utiliza es mas eficiente, continua usando el algoritmo
* CAS pero cuando falla almacena los valores pendientes en un objeto Cell que luego será utilizado
*
* - LongAccumulator: Esta clase es una version mas especializada de LongAdder la cual nos permite trabajar con lambdas del tipo LongBinaryOperator
*/
public class Main {
private static final int MAX_THREADS = 10;
public static void main(String[] args) {
// Definimos un solo contador que será compartido por todos los threads
final AtomicCounter counter = new AtomicCounter();
// Creamos la lista de Futures que tendrá el resultado de cada Thread
List<Future<Integer>> results = new ArrayList<Future<Integer>>();
// Creamos un pool de 10 hilos
ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);
// Creamos 50 tareas y cada tarea usa el contador compartido por todos
for (int i = 0; i < 50; i++) {
Callable<Integer> worker = new Task(counter);
Future<Integer> submit = executor.submit(worker);
results.add(submit);
}
// Iniciamos la finalización de forma ordenada y no aceptarán nuevas tareas
executor.shutdown();
// Esperamos que terminen todos los Threads
while (!executor.isTerminated()) {
}
// Obtenemos los valores en un Set que no acepta repetidos
Set<Integer> set = new HashSet<Integer>();
// Recorremos los resultados
for (Future<Integer> future : results) {
try {
set.add(future.get());
} catch (InterruptedException | ExecutionException e) {
// Log and Handle exception
e.printStackTrace();
}
}
// Chequeo para ver que todas las operaciones hayan sido realizadas de forma atómica
if (results.size() != set.size()) {
throw new RuntimeException("There are some entries with the same value!!!");
}
}
/*
* Usamos un wrapper el cual contiene una instancia de AtomicInteger
* Si nos usáramos AtomicInteger podría suceder que las operaciones no se ejecuten de forma atómica por ende podríamos perder numeros.
*/
private static final class AtomicCounter {
private AtomicInteger value = new AtomicInteger();
public int increment() {
// incrementAndGet: incrementa en 1 el valor actual y lo devuelve.
return value.incrementAndGet();
}
}
private static final class Task implements Callable<Integer> {
private AtomicCounter counter;
public Task(AtomicCounter counter) {
this.counter = counter;
}
@Override
public Integer call() throws Exception {
int number = counter.increment();
System.out.println("Task number:" + number);
return number;
}
}
}