Ejemplo simple con CyclicBarrier

De ChuWiki


Cuando trabajamos con hilos en Java, nos puede interesar que varios hilos comiencen su ejecución a la vez o nos puede interesar esperar a que terminen todos los hilos que hemos lanzado previamente. Esperar por un solo hilo no es problema, ese hilo tiene el método join() que hace exactamente eso, esperar a que termine

// Arranque de hilo
hilo.start();
...
// Espera a que termine
hilo.join();

sin embargo, esperar por muchos hilos nos implicaría hacer muchas llamadas join(), una por cada hilo arrancado.

Para arrancar varios hilos a la vez o para esperar que todos ellos terminen, java nos ofrece la clase CyclicBarrier. Esta clase se instancia pasándole en el constructor cuántos hilos debe sincronizar. Los hilos deben llamar al método await() de CyclicBarrier y se quedarán ahí detenidos. CyclicBarrier los liberará cuando tenga tantos hilos a la espera como se le haya indicado en el constructor.

Si queremos que varios hilos empiecen a la vez (por ejemplo, 5 hilos), tendremos que hacer lo siguiente

// Sincroniza 5 hilos
CyclicBarrier barrera = new CyclicBarrier(5);

y cada hilo debe hacer la siguiente llamada

public void run () {
   try {
       barrera.await();  // Se queda bloqueado hasta que 5 hilos hagan esta llamada.
   } catch (Exception e) {
      ...
   }
   // Aquí el código del hilo.
}

Si queremos esperar que los 5 hilos terminen su ejecución, debemos hacer una CyclicBarrier de 6, los 5 hilos más otro para nuestro propio hilo. El código sería

// Sincroniza 5 hilos secundarios + el hilo principal
CyclicBarrier barrera = new CyclicBarrier(6);

Los 5 hilos deberían hacer

public void run () {
   // Aquí el código del hilo.
   try {
       barrera.await();  // Se queda bloqueado hasta que 6 hilos hagan esta llamada.
   } catch (Exception e) {
      ...
   }
}

y en nuestro código principal, después de lanzar los 5 hilos, haríamos

   try {
       barrera.await();  // Se queda bloqueado hasta que los 5 hilos terminen.
   } catch (Exception e) {
      ...
   }

La "posible pega" a este mecanismo es que los 5 hilos se quedan bloqueado sin terminar hasta que el hilo principal hace también su llamada a await().

El siguiente ejemplo completo hace que nuestro main() lance 100 hilos que se quedan bloqueados hasta que nuestro main() les da la señal de arrancar (En realidad, hasta que se llama barreraInicio.await() por 101 vez, cualquier hilo puede ser el 101 en hacer la llamada, pero desde luego, todos esperarán por el último y empezarán a la vez). Una vez arrancados, el main() espera a que termine la ejecución de todos ellos (barreraFin.await()).

package com.chuidiang.ejemplos;

import java.util.concurrent.CyclicBarrier;

public class PruebaCyclicBarrier {

    public static void main(String[] args) {
        int numeroHilos = 100;
        final CyclicBarrier barreraInicio = new CyclicBarrier(numeroHilos + 1);
        final CyclicBarrier barreraFin = new CyclicBarrier(numeroHilos + 1);

        for (int i = 0; i < numeroHilos; i++) {
            Thread hilo = new Thread() {
                public void run() {
                    try {
                        barreraInicio.await();
                        System.out.println("hilo ejecutandose");
                        barreraFin.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            hilo.start();
        }

        try {
            System.out.println("levanto barrera");
            barreraInicio.await();
            barreraFin.await();
            System.out.println("todo acabado");
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}