并发包中的CountDownLatch同步工具

一、CountDownLatch同步工具

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待


你可以向CounDownLatch对象设置一个初始计数器,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值达到0。其他任务在结束其工作时,可以在该对象上调用countDown() 来减小这个计数值。 CountDownLatch被设计为只触发一次,计数值不能被重置。如果需要重置计数值。则可以使用CyclicBarrier


1.1 API

API 描述
CountDownLatch(int count) 构造一个用给定计数初始化的 CountDownLatch。
void await() 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
boolean await(long timeout, TimeUnit unit) 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间
void countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程;该方法并不会阻塞,只有await()调用会被阻塞,直到计数值达到0
long getCount() 返回当前计数

1.2 案例分析

应用场景:

countDownLatch的典型用法是将一个程序分为n 个互相独立的可解决任务,并创建值为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown()。 等待问题被解决的任务在这个锁存器上调用 await(),将它们自己拦住,直至锁存器计数结束。

案例一:

package test;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;


class TaskPortion implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private static Random random = new Random(47);
    private final CountDownLatch countDownLatch;//同一个countDownLatch

    public TaskPortion(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            doWork();
            countDownLatch.countDown();//完成对应的功能,coutDown()让计数值减一
        } catch (InterruptedException e) {

        }
    }

    public void doWork() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(random.nextInt(2000));
        System.out.println(this + "completed");
    }

    public String toString() {
        return String.format("%1$-3d ", id);
    }
}

class WaitingTask implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final CountDownLatch countDownLatch;

    public WaitingTask(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            //任何在这个对象上调用wait()的方法都将阻塞,直至这个计数值到达0
            countDownLatch.await();
            System.out.println("Latch barrier passed for " + this);
        } catch (InterruptedException e) {
            System.out.print(this + "interrupted");
        }
    }

    public String toString() {
        return String.format("WaitingTask %1$-3d", id);
    }
}

public class CountDownLatchDemo {
    static final int SIZE = 10;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);//计数值为100
        for (int i = 0; i < 2; i++) { //10个等待
            executorService.execute(new WaitingTask(countDownLatch));
        }
        for (int i = 0; i < SIZE; i++) { //100 任务
            executorService.execute(new TaskPortion(countDownLatch));
        }
        System.out.println("Launched all tasks");
        executorService.shutdown();
    }
}

打印结果

Launched all tasks
9   completed
7   completed
5   completed
8   completed
1   completed
2   completed
6   completed
4   completed
0   completed
3   completed
Latch barrier passed for WaitingTask 0  
Latch barrier passed for WaitingTask 1  

TaskPortion将随机地休眠一段时间,以模拟这部分工作的完成,而WaitingTask表示系统中必须等待的部分,它要等待到问题的初始化部分完成为止。所有任务都使用了在main()中定义的同一个单一的CountDownLatch


案例二

使用两个CountDownLatch的情况


public class CountdownLatchTest {

    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        final CountDownLatch cdOrder = new CountDownLatch(1);
        final CountDownLatch cdAnswer = new CountDownLatch(3);      
        for(int i=0;i<3;i++){
            Runnable runnable = new Runnable(){
                    public void run(){
                    try {
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "正准备接受命令");                     
                        cdOrder.await();
                        System.out.println("线程" + Thread.currentThread().getName() + 
                        "已接受命令");                               
                        Thread.sleep((long)(Math.random()*10000));  
                        System.out.println("线程" + Thread.currentThread().getName() + 
                                "回应命令处理结果");                        
                        cdAnswer.countDown();                       
                    } catch (Exception e) {
                        e.printStackTrace();
                    }               
                }
            };
            service.execute(runnable);
        }       
        try {
            Thread.sleep((long)(Math.random()*10000));

            System.out.println("线程" + Thread.currentThread().getName() + 
                    "即将发布命令");                      
            cdOrder.countDown();
            System.out.println("线程" + Thread.currentThread().getName() + 
            "已发送命令,正在等待结果");    
            cdAnswer.await();
            System.out.println("线程" + Thread.currentThread().getName() + 
            "已收到所有响应结果");   
        } catch (Exception e) {
            e.printStackTrace();
        }               
        service.shutdown();

    }
}

运行结果

线程pool-1-thread-1正准备接受命令
线程pool-1-thread-2正准备接受命令
线程pool-1-thread-3正准备接受命令
线程main即将发布命令
线程main已发送命令,正在等待结果
线程pool-1-thread-3已接受命令
线程pool-1-thread-1已接受命令
线程pool-1-thread-2已接受命令
线程pool-1-thread-2回应命令处理结果
线程pool-1-thread-3回应命令处理结果
线程pool-1-thread-1回应命令处理结果
线程main已收到所有响应结果

案例三

应用场景: 计算各个品牌的总库存。

/*
 * CountDownLatch :闭锁,在完成某些运算是,只有其他所有线程的运算全部完成,当前运算才继续执行
 */
public class TestCountDownLatch {

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(50);
        LatchDemo ld = new LatchDemo(latch);

        long start = System.currentTimeMillis();

        for (int i = 0; i < 50; i++) {
            new Thread(ld).start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
        }

        long end = System.currentTimeMillis();

        System.out.println("耗费时间为:" + (end - start));
    }

}

class LatchDemo implements Runnable {

    private CountDownLatch latch;

    public LatchDemo(CountDownLatch latch) {
        this.latch = latch;
    }

    @Override
    public void run() {

        try {
            for (int i = 0; i < 50000; i++) {
                if (i % 2 == 0) {
                    System.out.println(i);
                }
            }
        } finally {
            latch.countDown();
        }

    }

}

1.4 用法补充

示例用法: 下面给出了两个类,其中一组 worker 线程使用了两个倒计数锁存器:

第一个类是一个启动信号,在 `driver` 为继续执行` worker` 做好准备之前,它会阻止所有的 `worker` 继续执行。
第二个类是一个完成信号,它允许 `driver` 在完成所有 `worker` 之前一直等待。
 class Driver { // ...
   void main() throws InterruptedException {
     CountDownLatch startSignal = new CountDownLatch(1);
     CountDownLatch doneSignal = new CountDownLatch(N);

     for (int i = 0; i < N; ++i) // create and start threads
       new Thread(new Worker(startSignal, doneSignal)).start();

     doSomethingElse();            // don't let run yet
     startSignal.countDown();      // let all threads proceed
     doSomethingElse();
     doneSignal.await();           // wait for all to finish
   }
 }

 class Worker implements Runnable {
   private final CountDownLatch startSignal;
   private final CountDownLatch doneSignal;
   Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
      this.startSignal = startSignal;
      this.doneSignal = doneSignal;
   }
   public void run() {
      try {
        startSignal.await();
        doWork();
        doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
   }

   void doWork() { ... }
 }

另一种典型用法是,将一个问题分成 N 个部分,用执行每个部分并让锁存器倒计数的 Runnable来描述每个部分,然后将所有 Runnable 加入到 Executor 队列。当所有的子部分完成后,协调线程就能够通过 await。(当线程必须用这种方法反复倒计数时,可改为使用 CyclicBarrier。)

 class Driver2 { // ...
   void main() throws InterruptedException {
     CountDownLatch doneSignal = new CountDownLatch(N);
     Executor e = ...

     for (int i = 0; i < N; ++i) // create and start threads
       e.execute(new WorkerRunnable(doneSignal, i));

     doneSignal.await();           // wait for all to finish
   }
 }

 class WorkerRunnable implements Runnable {
   private final CountDownLatch doneSignal;
   private final int i;
   WorkerRunnable(CountDownLatch doneSignal, int i) {
      this.doneSignal = doneSignal;
      this.i = i;
   }
   public void run() {
      try {
        doWork(i);
        doneSignal.countDown();
      } catch (InterruptedException ex) {} // return;
   }

   void doWork() { ... }
 }

参考

  1. 张孝祥-Java多线程与并发库高级应用
  2. 《java编程思想》

猜你喜欢

转载自blog.csdn.net/qq_31156277/article/details/80516173
今日推荐