温故知新-多线程-forkjoin、CountDownLatch、CyclicBarrier、Semaphore用法
- Posted by 微博@Yangsc_o
- 原创文章,版权声明:自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
摘要
本文主要简单介绍forkjoin、CountDownLatch、CyclicBarrier、Semaphore的常见用法;
forkjoin
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。
这种思想和MapReduce很像(input --> split --> map --> reduce --> output)
主要有两步:
- 第一、任务切分;
- 第二、结果合并
它的模型大致是这样的:线程池中的每个线程都有自己的工作队列(PS:这一点和ThreadPoolExecutor不同,ThreadPoolExecutor是所有线程公用一个工作队列,所有线程都从这个工作队列中取任务),当自己队列中的任务都完成以后,会从其它线程的工作队列中偷一个任务执行,这样可以充分利用资源。
假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法的优点是充分利用线程进行并行计算,并减少了线程间的竞争,其缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new MyForkJoinTask(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum=" + sum + " 时间:" + (end - start));
}
}
class MyForkJoinTask extends RecursiveTask<Long> {
private Long start;
private Long end;
// 临界值
private Long temp = 10000L;
public MyForkJoinTask(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if ((end - start) < temp) {
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else { // forkjoin 递归
long middle = (start + end) / 2;
MyForkJoinTask task1 = new MyForkJoinTask(start, middle);
task1.fork();
MyForkJoinTask task2 = new MyForkJoinTask(middle + 1, end);
task2.fork();
return task1.join() + task2.join();
}
}
}
CountDownLatch
CountDownLatch 的方法不是很多,将它们一个个列举出来:
- await() throws InterruptedException:调用该方法的线程等到构造方法传入的 N 减到 0 的时候,才能继续往下执行;
- await(long timeout, TimeUnit unit):与上面的 await 方法功能一致,只不过这里有了时间限制,调用该方法的线程等到指定的 timeout 时间后,不管 N 是否减至为 0,都会继续往下执行;
- countDown():使 CountDownLatch 初始值 N 减 1;
- long getCount():获取当前 CountDownLatch 维护的值
public class CountDownLatchDemo {
private static CountDownLatch startSignal = new CountDownLatch(1);
//用来表示裁判员需要维护的是6个运动员
private static CountDownLatch endSignal = new CountDownLatch(6);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
executorService.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 运动员等待裁判员响哨!!!");
startSignal.await();
System.out.println(Thread.currentThread().getName() + "正在全力冲刺");
endSignal.countDown();// 数量-1
System.out.println(Thread.currentThread().getName() + " 到达终点");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
System.out.println("裁判员发号施令啦!!!");
startSignal.countDown(); // 数量-1
endSignal.await();
System.out.println("所有运动员到达终点,比赛结束!");
executorService.shutdown();
}
@SneakyThrows
public static void test0() {
// 总数是6,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " Go out");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
CyclicBarrier
当多个线程都达到了指定点后,才能继续往下继续执行。这就有点像报数的感觉,假设 6 个线程就相当于 6 个运动员,到赛道起点时会报数进行统计,如果刚好是 6 的话,这一波就凑齐了,才能往下执行。**CyclicBarrier 在使用一次后,下面依然有效,可以继续当做计数器使用,这是与 CountDownLatch 的区别之一。**这里的 6 个线程,也就是计数器的初始值 6,是通过 CyclicBarrier 的构造方法传入的。
下面来看下 CyclicBarrier 的主要方法:
// 等到所有的线程都到达指定的临界点 await() throws InterruptedException, BrokenBarrierException
// 与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止 await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException
//获取当前有多少个线程阻塞等待在临界点上 int getNumberWaiting()
//用于查询阻塞等待的线程是否被中断 boolean isBroken()
public class CyclicBarrierDemo {
public static void main(String[] args) {
test();
}
public static void test() {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()-> {
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <=7 ; i++) {
final int temp = i;
// lambda能操作到 i 吗
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
cyclicBarrier.await(); // 等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。很多年以来,我都觉得从字面上很难理解Semaphore所表达的含义,只能把它比作是控制流量的红绿灯,比如XX马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入XX马路,但是如果前一百辆中有五辆车已经离开了XX马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
Semaphore 类中比较重要的几个方法:
- public void acquire(): 用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许
可。 - public void acquire(int permits):获取 permits 个许可
- public void release() { } :释放许可。注意,在释放许可之前,必须先获获得许可。
- public void release(int permits) { }:释放 permits 个许可
上面 4 个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法13/04/2018 Page 86 of 283 - public boolean tryAcquire():尝试获取一个许可,若获取成功,则立即返回 true,若获取失
败,则立即返回 false - public boolean tryAcquire(long timeout, TimeUnit unit):尝试获取一个许可,若在指定的
时间内获取成功,则立即返回 true,否则则立即返回 false - public boolean tryAcquire(int permits):尝试获取 permits 个许可,若获取成功,则立即返
回 true,若获取失败,则立即返回 false - public boolean tryAcquire(int permits, long timeout, TimeUnit unit): 尝试获取 permits
个许可,若在指定的时间内获取成功,则立即返回 true,否则则立即返回 false - 还可以通过 availablePermits()方法得到可用的许可数目。
应用场景
Semaphore可以用于做流量控制,特别公用资源有限的应用场景;
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量:停车位! 限流!
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
// acquire() 得到
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // release() 释放
}
},String.valueOf(i)).start();
}
}
}
预告:下一篇会分析一下AQS的实现原理,因为CountDownLatch、CyclicBarrier、Semaphore都是基于AQS实现的;
参考
JDK 7 中的 Fork/Join 模式
一文秒懂 Java Fork/Join
并发工具类(三)控制并发线程数的Semaphore
你的鼓励也是我创作的动力
温故知新-多线程-forkjoin、CountDownLatch、CyclicBarrier、Semaphore用法的更多相关文章
- 多线程中 CountDownLatch CyclicBarrier Semaphore的使用
CountDownLatch 调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行.也可以传入时间,表示时间到之后,count还没有为0的时候,就会继续执行. package ...
- 并发包下常见的同步工具类详解(CountDownLatch,CyclicBarrier,Semaphore)
目录 1. 前言 2. 闭锁CountDownLatch 2.1 CountDownLatch功能简介 2.2 使用CountDownLatch 2.3 CountDownLatch原理浅析 3.循环 ...
- CountDownLatch/CyclicBarrier/Semaphore 使用过吗?
CountDownLatch/CyclicBarrier/Semaphore 使用过吗?下面详细介绍用法: 一,(等待多线程完成的)CountDownLatch 背景; countDownLatch ...
- 并发包下常见的同步工具类(CountDownLatch,CyclicBarrier,Semaphore)
在实际开发中,碰上CPU密集且执行时间非常耗时的任务,通常我们会选择将该任务进行分割,以多线程方式同时执行若干个子任务,等这些子任务都执行完后再将所得的结果进行合并.这正是著名的map-reduce思 ...
- Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo
Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo CountDownLatch countDownLatch这个类使一个线程等待其他线程 ...
- JUC 并发编程--04 常用的辅助类CountDownLatch , CyclicBarrier , Semaphore , 读写锁 , 阻塞队列,CompletableFuture(异步回调)
CountDownLatch 相当于一个减法计数器, 构造方法指定一个数字,比如6, 一个线程执行一次,这个数字减1, 当变为0 的时候, await()方法,才开始往下执行,, 看这个例子 Cycl ...
- 高并发第十单:J.U.C AQS(AbstractQueuedSynchronizer) 组件:CountDownLatch. CyclicBarrier .Semaphore
这里有一篇介绍AQS的文章 非常好: Java并发之AQS详解 AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.lang.concurrent)下lo ...
- Java中的4个并发工具类 CountDownLatch CyclicBarrier Semaphore Exchanger
在 java.util.concurrent 包中提供了 4 个有用的并发工具类 CountDownLatch 允许一个或多个线程等待其他线程完成操作,课题点 Thread 类的 join() 方法 ...
- CountDownLatch CyclicBarrier Semaphore 比较
document CountDownLatch A synchronization aid that allows one or more threads to wait until a set of ...
随机推荐
- Redux:store
Store是一个对象.他有如下职责: 1.存放state 2.对外提供访问state的接口: getState() 3.允许state更新:dispatch(action) 4.注册监听器: subs ...
- Django之Middleware中间件方法使用
自定义中间件五个方法(部分方法)实例 自定义中间件项目: 模板Templates login.html {% load static %} <!DOCTYPE html> <html ...
- C# 数据操作系列 - 13 SugarSql初探
0. 前言 前言,暂时挥别NHibernate(虽然我突然发现这玩意还挺有意思的,不过看得人不多).大步进入了有很多小伙伴向我安利的SQLSugar,嗯,我一直叫SugarSQL,好像是这个吧? 这是 ...
- 图像分析之梯度L0范数平滑
本文是Image Smoothing via L0 Gradient Minimization一文的笔记.L0 Gradient Smoothing的formulation与TV和WLS等基于变分的模 ...
- JUC整理笔记二之聊聊volatile
要想学好JUC,还得先了解 volatile 这个关键字.了解 volatile ,我们从一个例子开始吧. 本文不会很详细去说java内存模型,只是很简单地学习一下volatile 一个例子 pack ...
- [256个管理学理论]001.蝴蝶效应(Butterfly Effect)
蝴蝶效应(Butterfly Effect) 来自于大洋彼岸的让你看不懂的解释: 蝴蝶效应是指在一个动力系统中,初始条件下微小的变化能带动整个系统的长期的巨大的连锁反应,是一种混沌的现象.“蝴蝶效应” ...
- [256个管理学理论]006.刺猬效应(Hedgehog Effect)
刺猬效应(Hedgehog Effect) 来自于大洋彼岸的让你看不懂的解释: 刺猬效应(刺猬法则)就是人际交往中的“心理距离效应”.人与人之间都应该保持这条底线,过犹不及. 刺猬效应强调的就是人际交 ...
- [工具推荐]_iOS音频批量转换
通常为了便于市场推广和获得更高的下载量,产品安装包的体积是越小越好.那么问题来了,要如何才能做到使安装包的体积最小化呢.根据产品的组成成分,最终可以大概分为,代码,图片,视频,音频等这几部分,今天我们 ...
- Java中的集合(三)继承Collection的Queue接口
Java中的集合(三)继承Collection的Queue接口 一.Queue介绍 Queue接口继承自Collection接口,是Java中定义的一种队列数据结构,元素是有序的(按插入顺序排序),先 ...
- 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)
上一篇(https://www.cnblogs.com/meowv/p/12971041.html)使用HtmlAgilityPack抓取壁纸数据成功将图片存入数据库,本篇继续来完成一个全网各大平台的 ...