CyclicBarrier
También se llama valla de bucle, se puede lograr para permitir que un conjunto de subprocesos que se ejecutan en la valla y función de bloqueo hasta que todos los hilos han llegado a la valla y luego realizar juntos. "Loop" significa que CyclicBarrier
se pueden utilizar en varias ocasiones, en comparación con CountDownLatch
sólo pueden utilizarse una vez, CyclicBarrier
se puede ahorrar una gran cantidad de recursos, y también pueden pasar tarea en el constructor, para llevar a cabo esta tarea cuando se cumplen las condiciones de la cerca. CyclicBarrier
Es utilizado ReentrantLock
, el método principal será bloqueado en el momento de la ejecución, por lo tanto, el rendimiento concurrente no es muy alto.
1. campos relacionados
//重入锁,CyclicBarrier内部通过重入锁实现线程安全
private final ReentrantLock lock = new ReentrantLock();
//线程阻塞时的等待条件
private final Condition trip = lock.newCondition();
//需要等待的线程数
private final int parties;
//栅栏打开之后首先执行的任务
private final Runnable barrierCommand;
//记录当前的分代标记
private Generation generation = new Generation();
//当前还需要等待多少个线程运行到栅栏位置
private int count;
Tenga en cuenta que el generation
campo utilizado para marcar la valla actual en la generación. Cuando se cumplen ciertas condiciones (por ejemplo, los llamados reset
métodos, etc., o se abre una valla), el estado cambia a la siguiente cerca, es en realidad new
un nuevo Generation
objeto, que es CyclicBarrier
una clase interna, el código es muy simple, como sigue:
private static class Generation {
boolean broken = false; //标记栅栏是否被破坏
}
En el uso real, vamos a utilizar generation
los campos de determinar si la corriente en el mismo generacional, utilizando broker
campo determina si la barrera se destruye.
2. Constructor
CyclicBarrier
Hay dos constructor sobrecargado, el constructor simplemente relacionada con los campos anteriores son inicializados como sigue:
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
3. El método de núcleo
await
await
Es el método más comúnmente utilizado de desarrollo de la mismaCountDownLatch
, al igualCyclicBarrier
que también proporciona dosawait
métodos, uno sin parámetros, con un parámetro de tiempo de espera, simplemente llame al interior de un poco dedowait
método:
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
Luego tomar un aspecto crítico dowait
método:
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//加重入锁
lock.lock();
try {
//首先获取年龄代信息
final Generation g = generation;
//如果栅栏状态被破坏,抛出异常,例如先启动的线程调用了breakBarrier方法,后启动的线程就能够看到g.broker=true
if (g.broken)
throw new BrokenBarrierException();
//检测线程的中断状态,如果线程设置了中断状态,则通过breakBarrier设置栅栏为已破坏状态,并唤醒其他线程
//如果这里能够检测到中断状态,那只可能是在await方法外部设置的
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//每调用一次await,就将需要等待的线程数减1
int index = --count;
//index=0表示这是最后一个到达的线程,由该线程执行下面的逻辑
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//如果在构造器中传入了第二个任务参数,就在放开栅栏前先执行这个任务
if (command != null)
command.run();
ranAction = true;
//正常结束,需要唤醒阻塞的线程,并换代
nextGeneration();
return 0;
} finally {
//try代码块如果正常执行,ranAction就一定等于true,而try代码块唯一可能发生异常的地方就是command.run(),
//因此这里为了保证在任务执行失败时,将栅栏标记为已破坏,唤醒阻塞线程
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
//没有设置超时标记,就加入等待队列
if (!timed)
trip.await();
//设置了超时标记,但目前还没有超时,则继续等待
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//如果线程等待的过程中被中断,会执行到这里
//g == generation表示当前还在同一个年龄分代中,!g.broker表示当前栅栏状态没有被破坏
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
//上面的条件不满足,说明:1)g!=generation,说明线程执行到这里时已经换代了
//2)没有换代,但是栅栏被破坏了
//无论哪种情况,都只是简单地设置一下当前线程的中断状态
Thread.currentThread().interrupt();
}
}
//栅栏被破坏,抛出异常
//注意,在breakBarrier方法中会唤醒所有等待条件的线程,这些线程会执行到这里,判断栅栏已经被破坏,都会抛出异常
if (g.broken)
throw new BrokenBarrierException();
//距离上一次设置g变量的值已经过去很长时间了,在执行过程中generation可能已经发生改变,
//当前线程还是前几代的,不需要再循环阻塞了,直接返回上一代剩余需要等待的线程数
//注意:代码中breakBarrier方法和nextGeneration方法都会唤醒阻塞的线程,但是breakBarrier在上一个判断就被拦截了,
//因此走到这里的有三种情况:
//a)最后一个线程正常执行,栅栏打开导致其他线程被唤醒;不属于当前代的线程直接返回,
//属于当前代的则可能因为没到栅栏开放条件要继续循环阻塞
//b)栅栏被重置(调用了reset方法),此时g!=negeration,全都直接返回
//c)线程等待超时了,不属于当前代的返回就可以了,属于当前代的则要设置generation.broken = true
if (g != generation)
return index;
//如果线程等待超时,标记栅栏为破坏状态并抛出异常,如果还没超时,则自旋后又重新阻塞
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//别忘了解锁
lock.unlock();
}
}
dowait
La lógica del método es: cada llamada await
será a la cuenta de hilos count
reducido 1
, el último hilo se count
reduce a 0
tiempo, dicho sea de paso, también llevan a cabo barrierCommand
tareas y especificados generation
cambiar a la siguiente generación, por supuesto, lo más importante es despertar antes de la valla en hilo bloqueado. Debido a trip
la correspondiente Condition
objeto no tiene lugar será modificada, por lo que trip.signalAll()
será todo despertar en la condición de espera de hilo, si el hilo en el proceso de esperar, los otros hilos se generation
actualizará a la siguiente generación, habrá hilo despertado algunos también pertenecen antes de esa situación generación.
La próxima será en dowait
algunos de los métodos utilizados para una breve introducción.
breakBarrier
dowait
Hay cuatro llamado método localbreakBarrier
, se puede ver en el nombre, que segeneration.broken
establecetrue
, además, restaurarácount
los valores y se despierta todo el hilo bloqueado:
private void breakBarrier() {
generation.broken = true;
count = parties;
//唤醒所有的阻塞线程
trip.signalAll();
}
Mirando el CyclicBarrier
código fuente, generation.broken
unificada breakBarrier
se establece en el enfoque true
, pero una vez generation.broken
establecido true
más tarde, después de comprobar el código va a lanzar una excepción a este estado, no hay manera de volver a utilizar la cerca (se puede llamar manualmente reset
reinicio), y el código fuente en las siguientes situaciones llamará al breakBarrier
método:
1) el hilo de corriente se interrumpe
2) pasado a través de la tarea constructor falla
condición interrumpida 3) esperando
4) hilo espera de tiempo de espera
5) llamar explícitamente al reset
método
nextGeneration
private void nextGeneration() {
// 唤醒所有的阻塞线程
trip.signalAll();
// 开启下一代
count = parties;
generation = new Generation();
}
reset
reset
El método principal es el final de esta generación, y el interruptor a la generación siguiente
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
Introducido aquí, en su conjunto CyclicBarrier
se ha introducido a punto de terminar, pero el proceso interno está lejos de ser simple, porque la lógica se encapsula en una gran parte AbstractQueuedSynchronizer
, esto define la clase cómo se bloquean los hilos en espera de la cola, sino también cómo ser despertado, así que si quieres hilo lógico visión que esperar, pero también hay que estudiar detenidamente AbstractQueuedSynchronizer
el trabajo. En este artículo se describe esta parte del contenido no estará detrás de si tiene tiempo será una presentación especial.