Java并发编程的艺术笔记(七)——CountDownLatch、CyclicBarrier详解
一.等待多线程完成的CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作,像加强版的join。(t.join()是等待t线程完成)
例:
(1)开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段;解析一个Excel里多个sheet的数据,此时可以考虑使用多线程,每个 线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。
(2)应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
(3)确保一个计算不会执行,直到所需要的资源被初始化。
CountDownLatch典型用法1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。
例:
package concurrent; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; /**
*@author 梦里南轲
*
*类说明:演示CountDownLatch,有5个初始化的线程,6个扣除点,
*初始化线程4个,每个1步
*单独的初始化线程1个,2步
*扣除完毕以后,主线程和业务线程才能继续自己的工作
*/
public class UseCountDownLatch { static CountDownLatch latch = new CountDownLatch(6); //初始化线程(只有一步,有4个)
private static class InitThread implements Runnable{ @Override
public void run() {
System.out.println("Thread_"+Thread.currentThread().getId()
+" ready init work......");
latch.countDown();//初始化线程完成工作了,countDown方法只扣减一次;
for(int i =0;i<2;i++) {
System.out.println("Thread_"+Thread.currentThread().getId()
+" ........continue do its work");
}
}
} //业务线程
private static class BusiThread implements Runnable{ @Override
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i =0;i<3;i++) {
System.out.println("BusiThread_"+Thread.currentThread().getId()
+" do business-----");
}
}
} public static void main(String[] args) throws InterruptedException {
// 单独的初始化线程,初始化分为2步,需要扣减两次
new Thread(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("Thread_" + Thread.currentThread().getId() + " ready init work step 1st......");
latch.countDown();// 每完成一步初始化工作,扣减一次
System.out.println("begin step 2nd.......");
TimeUnit.SECONDS.sleep(1);
System.out.println("Thread_" + Thread.currentThread().getId() + " ready init work step 2nd......");
latch.countDown();// 每完成一步初始化工作,扣减一次
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
new Thread(new BusiThread()).start();
for (int i = 0; i <= 3; i++) {
Thread thread = new Thread(new InitThread());
thread.start();
} latch.await();
System.out.println("Main do ites work........");
}
}
运行结果:
Thread_12 ready init work......
Thread_12 ........continue do its work
Thread_12 ........continue do its work
Thread_14 ready init work......
Thread_14 ........continue do its work
Thread_14 ........continue do its work
Thread_13 ready init work......
Thread_15 ready init work......
Thread_15 ........continue do its work
Thread_15 ........continue do its work
Thread_13 ........continue do its work
Thread_13 ........continue do its work
Thread_10 ready init work step 1st......
begin step 2nd.......
Thread_10 ready init work step 2nd......
Main do ites work........
BusiThread_11 do business-----
BusiThread_11 do business-----
BusiThread_11 do business-----
可以看出,初始化线程执行完了之后,主线程和业务线程才开始工作。
CountDownLatch典型用法2:实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。
例:
package concurrent; import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//裁判
CountDownLatch countDown = new CountDownLatch(1);
//5个选手
CountDownLatch await = new CountDownLatch(5); // 依次创建并启动处于等待状态的5个MyRunnable线程
for (int i = 0; i < 5; ++i) {
new Thread(new MyRunnable(countDown, await)).start();
} System.out.println("准备起跑......");
countDown.countDown();
System.out.println("选手开始赛跑");
await.await();
System.out.println("Bingo!");
}
}
package concurrent; import java.util.concurrent.CountDownLatch; public class MyRunnable implements Runnable { private final CountDownLatch countDown;
private final CountDownLatch await; public MyRunnable(CountDownLatch countDown, CountDownLatch await) {
this.countDown = countDown;
this.await = await;
} public void run() {
try {
countDown.await();//等待主线程执行完毕,获得开始执行信号...
System.out.println("跑完了");
await.countDown();//完成预期工作,发出完成信号... } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
准备起跑......
选手开始赛跑
跑完了
跑完了
跑完了
跑完了
跑完了
Bingo!
CountDownLatch的不足
CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,之后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
特有方法:
public CountDownLatch(int count); //指定计数的次数,只能被设置1次
public void countDown(); //调用此方法则计数减1
public void await() throws InterruptedException //调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断。
Public Long getCount(); //得到当前的计数
Public boolean await(long timeout, TimeUnit unit) //调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断或者计数器超时,返回false代表计数器超时
From Object Inherited:
Clone、equals、hashCode、notify、notifyALL、wait等。
二.同步屏障CyclicBarrier
CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。
当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
例:
package concurrent; import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier; /**
*@author 梦里南柯
*
*类说明:CyclicBarrier的使用,演示多个线程互相等待
*/
public class UseCyclicBarrier { private static CyclicBarrier barrier = new CyclicBarrier(5, new CollectThread()); private static ConcurrentHashMap<String, Long> resultMap = new ConcurrentHashMap<>();// 存放子线程工作结果的容器 public static void main(String[] args) {
System.out.println("屏障开启");
// 启动5个线程
for (int i = 0; i <= 4; i++) {
Thread thread = new Thread(new SubThread());
thread.start();
} } // 负责屏障开放以后的工作
private static class CollectThread implements Runnable { @Override
public void run() {
StringBuilder result = new StringBuilder();
for (Map.Entry<String, Long> workResult : resultMap.entrySet()) {
result.append("[" + workResult.getValue() + "]");
}
System.out.println("屏障开放, the result = " + result);
System.out.println("do other business........");
}
} // 工作线程
private static class SubThread implements Runnable { @Override
public void run() {
long id = Thread.currentThread().getId();// 线程本身的处理结果
resultMap.put(Thread.currentThread().getId() + "", id);
Random r = new Random();// 随机决定工作线程的是否睡眠
try {
if (r.nextBoolean()) {
Thread.sleep(2000 + id);
System.out.println("Thread_" + id + " ....do something ");
}
System.out.println(id + "到达,开始等待其他线程");
// 等待其他的工作线程
barrier.await();
Thread.sleep(1000 + id);
System.out.println("Thread_" + id + " ....do its business ");
} catch (Exception e) {
e.printStackTrace();
} }
}
}
运行结果:
屏障开启
12到达,开始等待其他线程
13到达,开始等待其他线程
Thread_10 ....do something
10到达,开始等待其他线程
Thread_11 ....do something
11到达,开始等待其他线程
Thread_14 ....do something
14到达,开始等待其他线程
屏障开放, the result = [11][12][13][14][10]
do other business........
Thread_10 ....do its business
Thread_11 ....do its business
Thread_12 ....do its business
Thread_13 ....do its business
Thread_14 ....do its business
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。例如,用一个Excel保存了用户所有银行流水,每个Sheet保存一个账户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水
package concurrent; import java.util.Map.Entry;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* 银行流水处理服务类
*使用线程池创建4个线程,分别计算每个sheet里的数据,每个sheet计算结果是1,再由
BankWaterService线程汇总4个sheet计算出的结果
* @authorftf
*
*/
public class BankWaterService implements Runnable {
/**
* 创建4个屏障,处理完之后执行当前类的run方法
*/
private CyclicBarrier c = new CyclicBarrier(4, this);
/**
* 假设只有4个sheet,所以只启动4个线程
*/
private Executor executor = Executors.newFixedThreadPool(4);
/**
* 保存每个sheet计算出的银流结果
*/
private ConcurrentHashMap<String, Integer> sheetBankWaterCount = new ConcurrentHashMap<String, Integer>(); private void count() {
for (int i = 0; i < 4; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
// 计算当前sheet的银流数据,计算代码省略
sheetBankWaterCount.put(Thread.currentThread().getName(), 1);
System.out.println(Thread.currentThread().getName() + "计算完成");
// 银流计算完成,插入一个屏障
try {
c.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
});
}
} @Override
public void run() {
int result = 0;
// 汇总每个sheet计算出的结果
for (Entry<String, Integer> sheet : sheetBankWaterCount.entrySet()) {
result += sheet.getValue();
}
// 将结果输出
sheetBankWaterCount.put("result", result);
System.out.println("计算结果:" + result);
} public static void main(String[] args) {
BankWaterService bankWaterCount = new BankWaterService();
bankWaterCount.count();
}
}
运行结果:
pool-1-thread-2计算完成
pool-1-thread-4计算完成
pool-1-thread-3计算完成
pool-1-thread-1计算完成
计算结果:4
三.CyclicBarrier和CountDownLatch的区别
CountDownLatch是一个或多个线程等待一组线程,需要外部线程(一个或多个线程)进行扣减,CyclicBarrier是线程间的相互等待,他们之间自己决定,是多个线程本身到达同一个地点。
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
四.控制并发线程数的Semaphore
适合做流量控制,尤其是资源有限的情况。如数据库连接。假如读几万个文件的数据,然后写到数据库,这时需要开启几十个线程,而数据库的连接数只有10个,这时就要做流量控制了。
package concurrent; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import org.omg.CORBA.PRIVATE_MEMBER; public class SemaphoreTest {
private Semaphore smp = new Semaphore(5); class TaskDemo implements Runnable{
private String id;
TaskDemo(String id){
this.id = id;
}
@Override
public void run(){
try {
smp.acquire();
System.out.println("Thread " + id + " is working");
Thread.sleep(1000);
smp.release();
System.out.println("Thread " + id + " is over");
} catch (InterruptedException e) {
}
}
} public static void main(String[] args){
SemaphoreTest semaphoreDemo = new SemaphoreTest();
final int THREAD_COUNT = 10;
ExecutorService se = Executors.newCachedThreadPool();
for (int i = 0; i < THREAD_COUNT; i++) {
se.submit(semaphoreDemo.new TaskDemo(String.valueOf(i)));
} se.shutdown();
}
}
运行结果:
Thread 0 is working
Thread 1 is working
Thread 2 is working
Thread 3 is working
Thread 4 is working
Thread 5 is working
Thread 4 is over
Thread 3 is over
Thread 6 is working
Thread 7 is working
Thread 8 is working
Thread 0 is over
Thread 2 is over
Thread 1 is over
Thread 9 is working
Thread 9 is over
Thread 5 is over
Thread 7 is over
Thread 6 is over
Thread 8 is over
Java并发编程的艺术笔记(七)——CountDownLatch、CyclicBarrier详解的更多相关文章
- 多线程的通信和同步(Java并发编程的艺术--笔记)
1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递. 2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...
- Java并发编程的艺术笔记(二)——wait/notify机制
一.概述 一个线程修改了一个对象的值,另一个线程感知到变化从而做出相应的操作.前者是生产者,后者是消费者. 等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用 ...
- Java并发编程(07):Fork/Join框架机制详解
本文源码:GitHub·点这里 || GitEE·点这里 一.Fork/Join框架 Java提供Fork/Join框架用于并行执行任务,核心的思想就是将一个大任务切分成多个小任务,然后汇总每个小任务 ...
- Java并发编程的艺术笔记(五)——Java中的锁
一.Lock接口的几个功能: 显示的获取和释放锁 尝试非阻塞的获取锁 能被中断的获取锁 超时获取锁 使用方式: Lock lock = new ReentrantLock(); lock.lock() ...
- Java并发编程的艺术笔记(三)——Thread.join()
t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒.并不影响同一时刻处在运行状态的其他线程.它能够使得t.join()中的t优先执行,当t执行完后才会执行其他线程.能够使得线程 ...
- Java并发编程的艺术(七)——线程间的通信
为什么需要线程间通信 让线程之间合作,提高运行效率. volatile和synchronized关键字 实现原理 这两个方式都是采用共享内存的方式进行通信,通过同步机制保证数据可见性和排他性. 特点 ...
- Java并发编程的艺术· 笔记(1)
目录 1.volatile的原理 2.Synchonized 3.无锁-偏向锁-轻量级锁-重量级锁 4.Java实现原子操作 1.volatile的原理 如何保持可见性: 1)将当前处理器缓存行的数据 ...
- Java并发编程的艺术笔记(九)——FutureTask详解
FutureTask是一种可以取消的异步的计算任务.它的计算是通过Callable实现的,多用于耗时的计算. 一.FutureTask的三种状态 二.get()和cancel()执行示意 三.使用 一 ...
- Java并发编程的艺术笔记(八)——线程池
一.线程池的主要处理流程 ThreadPoolExecutor执行execute方法分下面4种情况. 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步需要获 ...
随机推荐
- 多边形面积(Area_Of_Polygons)
原理: 任意多边形的面积可由任意一点与多边形上依次两点连线构成的三角形矢量面积求和得出. 分析: 由于给出的点是相对于我们的坐标原点的坐标,每个点实际上我们可以当作一个顶点相对于原点的向量,如下图所示 ...
- 日志:slf4j+logback 的配置与使用
1. 常用日志组件和选择 java开发日志处理是发现和调试bug所 必不可少的,那么现在企业中常用的日志组件有哪些呢,JCL . JUL. SLF4j.Log4j. Log4j2 . Logbac ...
- 64位Win7安装Oracle12C临时位置权限错误解决方案
今天装备安装Oracle12C体验一下,结果遇到问题:请确保当前用户具有访问临时位置所需的权限,无法继续安装,上网查了一下,解决方案如下: 第一步: 控制面板>所有控制面板项>管理工具 ...
- linux命令详解——yum
1.如果不知道确切名字可以:rpm -qa|grep pkgname 2.查看软件安装的文件:rpm -qpl pkgname 3.如果不知道提供某个软件的包是叫什么,可以使用类似下面的写法: yum ...
- linux命令详解——ftp
ftp服务器在网上较为常见,Linux ftp命令的功能是用命令的方式来控制在本地机和远程机之间传送文件,这里详细介绍Linux ftp命令的一些经常使用的命令,相信掌握了这些使用Linux 进行ft ...
- HTTP协议请求过程
HTTP协议请求过程分析 步骤:用户输入URL,以www.yxh.com为例,获得主机名后,进行DNS域名解析, 首先,浏览器自身会查找自己缓存,没有的话,寻找本机的hosts文件,本机hosts没有 ...
- 全球首次!玩5G日本来了一波骚操作
5G基站信号覆盖范围较小是5G技术应用中需要面临的问题之一,从目前的报道来看,在人口密集的城市中其理想覆盖范围只有250米左右,这也就意味着5G基站的数量和密度相比4G要成倍的增加. 可以想象,当5G ...
- 【CERC 2014 E】2048
题意 2048曾经是一款风靡全球的小游戏. 今天,我们换一种方式来玩这个小游戏. 你有一个双端队列,你只能把元素从左端或从右端放入双端队列中.一旦放入就不得取出.放入后,若队列中有连续两个相同的元素, ...
- 心态炸了,我再写一个4.1.0版本的SND实例
maven依赖 <!-- Tests --><dependency> <groupId>org.springframework</groupId> &l ...
- 如何优雅高效的写博客(Sublime + Markdown + Evernote)
如何优雅高效的写博客(Sublime + Markdown + Evernote) 本文主要是参照了几位大神的博客加上自己捣鼓了半天,比较适合新手流畅阅读 非常感谢下面两位大神: @dc_726: h ...