Java高并发编程基础三大利器之CountDownLatch

开发 后端
CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就减1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上(调用await方法的线程)等待的线程就可以恢复工作了。

[[386772]]

 什么是CountDownLatch

CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就减1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上(调用await方法的线程)等待的线程就可以恢复工作了。

应用场景

CountDownLatch可以用来干什么呢?有什么应用场景?实际项目中有应用的场景吗?这应该才是大家比较关心的。我们先来看看官网提供的例子是如何进行应用的https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html 官方提供了两个demo我直接把它转成了图片顺带推荐下这个代码转图片的网址https://www.dute.org/code-snapshot 还挺好用的。

官网demo1

★The first is a start signal that prevents any worker from proceeding until the driver is ready for them to proceed; The second is a completion signal that allows the driver to wait until all workers have completed.”

  • 第一个开始信号(startSignal)会阻止任何工人(worker )开始工作,在司机到来之前。说白了就是司机没来工人就不能干活。
  • 第二个是完成信号 (doneSignal),允许司机 Driver 等待,直到所有的工人完成.说白了就是司机要等到所有工人完工为止。

官网demo2

★Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await.”

另一种典型的用法就是把一个大任务拆分N个部分,让多个线程(Worker)执行,每个线程(Worker)执行完自己的部分计数器就减1,当所有子部分都完成后,Driver 才继续向下执行才继续执行。就好比富士康手机加工的流水线一样,组装一步手机需要一条条的流水线来相互配合完成。一条条流水线(Worker),每条线都干自己的活。有的流水线是贴膜的,有的流水线是打螺丝的,有的流水线是质检的、有的流水线充电的、有的流水线贴膜的。等这些流水线都干完了就把一部手机组装完成了。

上面两个就是官方提供的demo,下面我再来两个我们平时开发中可以用到的栗子:

多个线程等待:模拟并发,让并发线程一起执行。

有时候我们写了接口想去压测下它,看看它的最大并发数大概是多少。当然我们可以使用Jmeter来进行压测,但是有时候我们不想去下载工具,其实就可以借助CountDownLatch来实现。

  1. /** 
  2.  * @author: 公众号:java金融 
  3.  */ 
  4. public class TestCountDownLatch1 { 
  5.      public static void main(String[] args) throws InterruptedException { 
  6.           CountDownLatch countDownLatch = new CountDownLatch(1); 
  7.           for (int i = 0; i < 5; i++) { 
  8.                new Thread(() -> { 
  9.                     try { 
  10.                          //所有请求都阻塞在这,等待 
  11.                          countDownLatch.await(); 
  12.                          // 调用测试接口 
  13.                          System.out.println(Thread.currentThread().getName() + "开始执行……"); 
  14.                     } catch (InterruptedException e) { 
  15.                          e.printStackTrace(); 
  16.                     } 
  17.                }).start(); 
  18.           } 
  19.           // 让请求都准备好 
  20.           Thread.sleep(2000); 
  21.           // 让所有请求统一请求 
  22.           countDownLatch.countDown(); 
  23.      } 

我们通过CountDownLatch.await(),让多个参与者线程启动后阻塞等待,然后在主线程 调用CountDownLatch.countdown() 将计数减为0,让所有线程一起往下执行;以此实现了多个线程在同一时刻并发执行,来模拟并发请求的目的。

单个线程等待:多个线程(任务)完成后,进行汇总合并

  1. /** 
  2.  * @author: 公众号:java金融 
  3.  */ 
  4. public class TestCountDownLatch1 { 
  5.      public static void main(String[] args) throws InterruptedException { 
  6.           int count = 3; 
  7.           CountDownLatch countDownLatch = new CountDownLatch(count); 
  8.           for (int i = 0; i < count; i++) { 
  9.                final int index = i; 
  10.                new Thread(() -> { 
  11.                     try { 
  12.                          Thread.sleep(1000 + ThreadLocalRandom.current().nextInt(1000)); 
  13.                          System.out.println("finish" + index + Thread.currentThread().getName()); 
  14.                     } catch (InterruptedException e) { 
  15.                          e.printStackTrace(); 
  16.                     }finally{ 
  17.                         countDownLatch.countDown(); 
  18.                     } 
  19.                }).start(); 
  20.           } 
  21.           countDownLatch.await();// 主线程在阻塞,当计数器==0,就唤醒主线程往下执行。 
  22.           System.out.println("主线程:在所有任务运行完成后,进行结果汇总"); 
  23.      } 

这种场景应该是用的最多了,比如我们打开一个电商的个人中心页面,我们需要调用,用户信息接口、用户订单接口、用户会员信息等接口,然后合并后一起给到前端,假设每个接口最长耗时为1s,如果我们同步调用的话最大耗时时间是3s,如果我们采用异步调用然后合并结果,所以最大的耗时时间是3s。每个接口调用返回数据后调用countDown方法,让计数器进行减1,当把计数器减为0时的这个线程会去唤醒主线程,让它继续往下走。

CountDownLatch 实现原理

CountDownLatch是通过AQS的state字段来实现的一个计数器,计数器的初始值(state的值)为new CountDownLatch设置的数量,每次调用countDown的时候,state的值会进行减1,最后某个线程将state值减为0时,会把调用了await()进行阻塞等待的线程进行唤醒。CountDownLatch重写了tryReleaseShared这个方法,只有当state这个字段被设置为0时,也就是tryReleaseShared返回true的情况就会执行doReleaseShared方法,把调用了await的线程进行唤醒。

  1. public final boolean releaseShared(int arg) { 
  2.       if (tryReleaseShared(arg)) { 
  3.           doReleaseShared(); 
  4.           return true
  5.       } 
  6.       return false
  7.   } 
  8. rotected boolean tryReleaseShared(int releases) { 
  9.           // Decrement count; signal when transition to zero 
  10.           for (;;) { 
  11.               int c = getState(); 
  12.               if (c == 0) 
  13.                   return false
  14.               int nextc = c-1; 
  15.               if (compareAndSetState(c, nextc)) 
  16.                   return nextc == 0; 
  17.           } 
  18.       } 

CountDownLatch的其他源码就不进行分析了, 相信看了这两篇文章《Java高并发编程基础之AQS》、《Java高并发编程基础三大利器之Semaphore》再来看这个还是比较轻松的。

总结

  • CountDownLatch不能重新初始化或者修改CountDownLatch内部计数器的值。
  • CountDownLatch和Semaphore在使用AQS的方式上很相似,在同步状态中都是保存的是当前的计数值。
  • CountDownLatch的作用就是允许一个或多个线程等待其他线程完成操作,看起来有点类似join() 方法,但其提供了比 join() 更加灵活的API。
  • CountDownLatch可以手动控制在n个线程里调用n次countDown()方法使计数器进行减一操作,也可以在一个线程里调用n次执行减一操作。
  • join() 的实现原理是不停检查join线程是否存活,如果 join 线程存活则让当前线程永远等待。所以两者之间相对来说还是CountDownLatch使用起来较为灵活。

本文转载自微信公众号「java金融」,可以通过以下二维码关注。转载本文请联系java金融公众号。

 

 

责任编辑:武晓燕 来源: java金融
相关推荐

2021-03-18 00:14:29

JavaCyclicBarri高并发

2021-03-04 07:24:24

JavaSemaphore高并发

2020-08-27 08:17:05

缓存高并发系统

2021-02-26 13:08:27

Java高并发AQS

2014-03-14 10:34:28

JavaJava并发

2019-09-16 09:23:34

高并发编程CountDownLaCyclicBarri

2022-07-02 08:40:00

并发编程

2020-09-21 06:53:41

NoSQL高并发面试

2019-11-07 09:20:29

Java线程操作系统

2022-11-27 08:12:11

RocketMQ源码工具类

2024-04-02 09:40:39

多线程Java原子性

2011-07-05 14:42:46

java

2021-08-05 07:58:22

并发编程包Task

2016-11-28 09:08:43

java系统异步非阻塞

2021-02-14 18:26:25

高并发大对象代码

2017-09-19 14:53:37

Java并发编程并发代码设计

2016-11-28 08:40:17

系统降级服务

2016-11-25 00:45:37

队列数据

2016-11-28 09:00:10

浏览器浏览器缓存服务端

2021-07-03 17:44:34

并发高并发原子性
点赞
收藏

51CTO技术栈公众号