Java线程间协作(二)

Posted by kyle on May 1, 2018

倒计时协调器:CountDownLatch

CountDownLatch可以用来实现一个(或者多个)线程等待其他线程完成一组特定的操作之后才继续运行。这组特定的操作被称为先决操作

CountDownLatch实现原理

CountDownLatch内部会维护一个用于表示未完成的先决操作数量的计数器。 CountDownLatch.countDown()每被执行一次就会使相应实例的计数器值减1。 CountDownLatch.await()相当于一个受保护的方法,其保护条件为“计数器值为0”,此时代表所有先决操作已执行完毕。

当计数器值不为0时,CountDownLatch.await()的执行线程会被暂停,这些线程就被称为相应CountDownLatch上的等待线程。CountDownLatch.countDown()相当于一个通知方法,它会在计数器值达到0的时候唤醒实例上的所有等待线程。

TIPS:当计数器的值达到0之后,该计数器的值就不再发生变化。此时,调用CountDownLatch.countDown()并不会导致异常的抛出,并且后续执行CountDownLatch.await()的线程也不会被暂停。因此,CountDownLatch的使用时一次性的:一个CountDownLatch实例只能够实现一次等待和唤醒。

CountDownLatch示例:
public class CountDownLatchExample {
    private static final CountDownLatch latch = new CountDownLatch(4);
    private static int data;

    public static void main(String[] args) throws InterruptedException {
        Thread workerThread = new Thread(() -> {
            for(int i = 0; i < 10; i++) {
                data = i;
                latch.countDown();
                // 暂停一段时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        });
        workerThread.start();
        latch.await();
        System.out.println(
        String.format("It's done. data=%d", data));
    }
}

该程序的输出总是如下:It’s done. data=4

栅栏(CyclicBarrier)

有时候多个线程可能需要相互等待对方执行到代码中的某个地方(集合点),这时这些线程才能够继续执行。JDK1.5开始引入的java.util.concurrent.CyclicBarrier可以用来实现这种等待。

CyclicBarrier实现原理

使用CyclicBarrier实现等待的线程被称为参与方(Party)。参与方只要执行CyclicBarrier.await()就可以实现等待。

CyclicBarrier内部维护了一个显示锁,这使得其总是可以在所有参与方中区分出最后一个执行CyclicBarrier.await()的线程,该线程被称为最后一个线程

除最后一个线程外的任何参与方执行CyclicBarrier.await()都会导致该线程被暂停。最后一个线程执行CyclicBarrier.await()会使得使用相应CyclicBarrier实例的其他所有参与方被唤醒。

CyclicBarrier内部使用了一个条件变量trip来实现等待/通知。CyclicBarrier内部实现了使用分代(Generation)的概念用于表示CyclicBarrier实例是可以重复使用的。除最后一个线程外的任何一个参与方都相当于一个等待线程,这些线程所使用的保护条件是“当前分代内,尚未执行await方法的参与方个数(parties)为0”。当前分代的初始状态是parties,其初始值等于参与方总数(通过构造器中的parties参数指定)。CyclicBarrier.await()每被执行一次会使相应实例的parties值减少1。最后一个线程相当于通知线程,他执行CyclicBarrier.await()会使相应实例的parties值变为0,此时该线程会先执行barrierAction.run(),然后 再执行trip.signalAll()来唤醒所有的等待线程。接着,开始下一个分代,即使得parties的值又重新恢复为其初始值。