Clases de
utilería para sincronización
La API de concurrencia tiene varias clases para la sincronización en
el paquete java.util.concurrent como Semaphore, CountDownLatch,
CyclicBarrier, Phaser y Exchanger.
Semaphore
Un semáforo tiene un contador que permite el acceso a un recurso
compartido, es similar a la interfaz Lock pero cuando un thread
quiere adquirir un semáforo, este verifica su contador y si es mayor
que cero, entonces el thread obtiene el semáforo y se reduce el
contador. Sin embargo, si el contador es cero el thread espera hasta
que se incremente el contador.
Ejemplo:
public class Recurso {
Semaphore semaphore = new Semaphore(2);//Indica que dos threads pueden acceder el recurso al mismo tiempo.
public void lock() {
semaphore.acquire();//Si el contador es cero el thread se duerme, de lo contrario se reduce y obtiene el acceso
System.out.println(“Comenzando, el thread ” + Thread.currentThread().getName() + “ tiene el lock”);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“Terminando, el thread ” + Thread.currentThread().getName() + “ tiene el lock”);
semaphore.release();//Libera el semaphore e incrementa el countador
}
}
.......
En el ejemplo, el método acquire() obtiene el semáforo si el
contador es mayor que cero, de lo contrario se espera hasta que se
incremente y reduce el contador, después el thread que obtiene el
semáforo ejecuta el recurso y finalmente, se ejecuta el método
release() y incrementa el contador.
Si el semáforo inicializa su contador con un valor de uno, entonces
se llama un semáforo binario y se comporta como la interfaz
java.util.concurrent.locks.Lock.
CountDownLatch
El CountdownLatch se puede utilizar para hacer esperar a un thread a
que N threads hayan completado alguna acción, o algún tipo de
acción se ha completado N veces. Tiene una contador y se inicializa
con un valor entero, este contador es el número de acciones a
esperar.
El CountdownLatch tiene el método countdown(), que reduce el
contador interno y el método await() bloquea o pone a dormir
un thread hasta que el contador llegue a cero.
Ejemplo:
public class Prueba implements Runnable {
private CountDownLatch controlador = new CountDownLatch(10);
private CyclicBarrier cyclicBarrier;
public Prueba(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
}
public void setQuestionAnswered(){
controlador.countDown();
}
@Override
public void run() {
try {
controller.await();
//Empieza la evaluacion de la prueba
cyclicBarrier.await();//Espera a todos los threads o partidas
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public double getTestResult() {
}
}
..........
final Prueba [] pruebas = new Prueba[10];
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,
new Runnable(){
public void run() {
//Obtiene el promedio de las pruebas
..........
}
});
for(int i=0; i<pruebas.length; i++)
pruebas[i] = new Prueba(cyclicBarrier);
..........
En el ejemplo la misma clase Prueba usada en el ejemplo anterior,
pero aquí el CyclicBarrier obtendrá el promedio de todas las
pruebas. Cuando todos los threads Prueba terminen el examen
CyclicBarrier ejecutará el objeto Runnable que se pasa como
parámetro y obtendrá el promedio de las pruebas.
Phaser
La clase Phaser es similar a CyclicBarrier y CountdownLatch pero más
flexible, las tareas o subprocesos se sincronizan en pasos o fases.
Las partes registradas para sincronizar en un Phaser pueden variar
con el tiempo, se puede cambiar de forma dinámica con los métodos
register(), bulkRegister().
El método arriveAndDeregister() notifica al Phaser que una tarea ha
terminado y que no participará en las futuras fases, se reduce el
número de partidos.
El método arriveAndAwaitAdvance() hace a una tarea esperar a que
todos los participantes terminen.
Ejemplo:
public class Prueba implements Runnable {
private Phaser phaser;
public Task(Phaser phaser) {
this. phaser = phaser;
}
@Override
public void run() {
try {
phaser.arriveAndAwaitAdvance();
//Empieza fase1
phaser.arriveAndAwaitAdvance();
//Empieza fase2
phaser.arriveAndAwaitAdvance();
//Empieza fase3
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
..........
final Prueba[] pruebas = new Prueba[10];
Phaser phaser = new Phaser(10);
for(int i=0; i<pruebas .length; i++)
pruebas[i] = new Prueba(phaser);
..........
En el ejemplo, el Phaser establece 10 partidas, cuando todas las
tareas lleguen al primer arriveAndAwaitAdvance() se iniciará la fase
1 y luego van a tener que esperar a todos los threads antes de
empezar la fase 2, y así sucesivamente.
Exchanger
La clase Exchanger sincroniza dos threads en un punto, cuando ambos
threads llegan a este punto intercambiar un objeto. Exchanger puede
ser visto como una forma bidireccional de un SynchronousQueue.
La clase Exchanger tiene el método exchange() el cual
intercambia datos entre threads.
El ejemplo de productor consumidor puede ser implementado con este
mecanismo.
Ejemplo:
public class Consumidor extends Thread {
private Exchanger <String> exchanger;
public Consumidor(Exchanger exchanger) {
this.exchanger = exchanger;
}
public void run() {
String mensaje = "";
while (!mensaje.equalsIgnoreCase("fin")) {
try {
mensaje = exchanger.exchange(mensaje);//Espera e intercambia el mensaje con el productor
System.out.print( mensaje + " ");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Productor extends Thread {
private Exchanger exchanger;
public Productor(Exchanger exchanger) {
this.exchanger = exchanger;
}
public void run() {
String [] mensajes = {"Hola", "mundo", "fin"};
for (String mensaje:mensajes) {
try {
exchanger.exchange(mensaje);//Espera e intercambia el mensaje con el consumidor;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
.......
Exchanger <String> exchanger = new Exchanger<String>();
Consumidor consumidor = new Consumidor(exchanger);
Productor productor = new Productor(exchanger);
consumidor.start();
productor.start();
.......
Este ejemplo es similar al de la parte 4, pero en lugar de tener una
clase que actúa como un monitor, aquí el Exchanger hace ese trabajo
y sincroniza los mensajes entre los threads.