JAVA并发编程(三):同步的辅助类之闭锁(CountDownLatch)与循环屏障(CyclicBarrier)

释放双眼,带上耳机,听听看~!

jdk1.5的concurrent包为我们提供了很多有用的辅助类,今天我们来学习一下

一、闭锁CountDownLatch

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。它的作用是,在完成某些运算时,只有其他所有线程的运算全部完成,当前线程的运算才继续执行。
CountDownLatch类只提供了一个构造器:


1
2
3
1public CountDownLatch(int count) {  };  //参数count为计数值
2
3

然后下面这3个方法是CountDownLatch类中最重要的方法:


1
2
3
4
5
1public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
2public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
3public void countDown() { };  //将count值减1
4
5

接下来我们看看下面的这个例子:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
1public class TestCountDownLatch {
2
3   public static void main(String[] args) {
4       final CountDownLatch latch = new CountDownLatch(50);
5       LatchDemo ld = new LatchDemo(latch);
6
7       long start = System.currentTimeMillis();
8
9       for (int i = 0; i < 50; i++) {
10          new Thread(ld).start();
11      }
12
13      try {
14          latch.await();
15      } catch (InterruptedException e) {
16      }
17
18      long end = System.currentTimeMillis();
19
20      System.out.println("耗费时间为:" + (end - start));
21  }
22
23}
24
25class LatchDemo implements Runnable {
26
27  private CountDownLatch latch;
28
29  public LatchDemo(CountDownLatch latch) {
30      this.latch = latch;
31  }
32
33  @Override
34  public void run() {
35
36      try {
37          for (int i = 0; i < 50000; i++) {
38              if (i % 2 == 0) {
39                  System.out.println(i);
40              }
41          }
42      } finally {
43          latch.countDown();
44      }
45
46  }
47
48}
49
50

通过CountDownLatch,我们就可以计算一批线程的执行时间。从这个例子中我们也可以看出:CountDownLatch中维护了一个int类型的变量。当前线程调用await()方法来使用这个辅助类,在这个变量成为0之后当前线程就会继续执行下去了。所以我们在构造器里填入的变量要跟等待的线程数量相同。

二、CyclicBarrier


1
2
3
1CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
2
3

CyclicBarrier提供2个构造器:


1
2
3
4
5
6
7
1public CyclicBarrier(int parties, Runnable barrierAction) {
2}
3
4public CyclicBarrier(int parties) {
5}
6
7

参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。
然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:


1
2
3
4
1public int await() throws InterruptedException, BrokenBarrierException { };
2public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
3
4

第一个版本比较常用,用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;

第二个版本是让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1public class Test {
2    public static void main(String[] args) {
3        int N = 4;
4        CyclicBarrier barrier  = new CyclicBarrier(N);
5        for(int i=0;i<N;i++)
6            new Writer(barrier).start();
7    }
8    static class Writer extends Thread{
9        private CyclicBarrier cyclicBarrier;
10        public Writer(CyclicBarrier cyclicBarrier) {
11            this.cyclicBarrier = cyclicBarrier;
12        }
13
14        @Override
15        public void run() {
16            System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");
17            try {
18                Thread.sleep(5000);      //以睡眠来模拟写入数据操作
19                System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");
20                cyclicBarrier.await();
21            } catch (InterruptedException e) {
22                e.printStackTrace();
23            }catch(BrokenBarrierException e){
24                e.printStackTrace();
25            }
26            System.out.println("所有线程写入完毕,继续处理其他任务...");
27        }
28    }
29}
30
31

执行结果


1
2
3
4
5
6
7
8
9
10
11
12
13
14
1线程Thread-0正在写入数据...
2线程Thread-3正在写入数据...
3线程Thread-2正在写入数据...
4线程Thread-1正在写入数据...
5线程Thread-2写入数据完毕,等待其他线程写入完毕
6线程Thread-0写入数据完毕,等待其他线程写入完毕
7线程Thread-3写入数据完毕,等待其他线程写入完毕
8线程Thread-1写入数据完毕,等待其他线程写入完毕
9所有线程写入完毕,继续处理其他任务...
10所有线程写入完毕,继续处理其他任务...
11所有线程写入完毕,继续处理其他任务...
12所有线程写入完毕,继续处理其他任务...
13
14

CyclicBarrier和CountDownLatch的区别

  • CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器是可以重用的。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
  • CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。比如以下代码执行完之后会返回true。

参考文章

  • Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
  • 并发工具类(二)同步屏障CyclicBarrier

本文作者: catalinaLi
本文链接: http://catalinali.top/2018/helloLatch/

给TA打赏
共{{data.count}}人
人已打赏
安全技术

用node.js做cluster,监听异常的邮件提醒服务

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索