多线程是我们学习路上必不可少的一块拦路石,撬开这块石头,就需要掌握好JUC。JUC下的一些并发同步特性在之前的文章里也写过,具体如下:
多线程基础知识
什么是JMM以及volatile的特性
详谈CAS以及ABA问题
AQS的了解,对于AQS是一个比较重要的知识点,下面这几篇文章可以帮助你通俗易懂的了解AQS是如何在底层执行工作的
谈谈你对AQS的了解
总的来说AQS在并发里相当于人的大脑,里边一个state,一个当前得锁的线程,一个FIFO的阻塞队列就把线程的安全解决了,
然后接下来说今天的主题 多线程下countDownLatch和CyclicBarrier是如何并发执行,以及它们之间的区别:
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的
看完后你会发现CountDownLatch和CyclicBarrier两者的await方法是会阻塞当前线程的,因此如果在线程池中使用CyclicBarrier,如果线程池的core大小小于parties,线程池中的线程遍一直处于阻塞状态,相反CountDownLatch的countDown()在执行后,完美退出当前线程,但是在开启调用CountDownLatch.await()的时候,需要在子线程中,否则就阻塞了当前线程。
上面的如果看的不太了解,则可以看看接下来的这两个具体代码对比:
CyclicBarrier:
1、CyclicBarrier:一个同步辅助类,用于协调多个子线程,让多个子线程在这个屏障前等待,直到所有子线程都到达了这个屏障时,再一起继续执行后面的动作。
2、使用场景举例:
年末公司组织团建,要求每一位员工周六上午8点【自驾车】到公司门口集合,然后【自驾车】前往目的地。
在这个案例中,公司作为主线程,员工作为子线程。
package com.test.spring.support;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author javaloveiphone
* @date 创建时间:2017年1月25日 上午10:59:11
* @Description:
*/
public class Company {
public static void main(String[] args) throws InterruptedException {
//员工数量
int count = 5;
//创建计数器
CyclicBarrier barrier = new CyclicBarrier(count+1);
//创建线程池,可以通过以下方式创建
//ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,1,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(count));
ExecutorService threadPool = Executors.newFixedThreadPool(count);
System.out.println("公司发送通知,每一位员工在周六早上8点【自驾车】到公司大门口集合");
for(int i =0;i<count ;i++){
//将子线程添加进线程池执行
threadPool.execute(new Employee(barrier,i+1));
Thread.sleep(10);
}
try {
//阻塞当前线程,直到所有员工到达公司大门口之后才执行
barrier.await();
Thread.sleep(10);
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
//latch.await(long timeout, TimeUnit unit)
System.out.println("所有员工已经到达公司大门口,公司领导一并【自驾车】同员工前往活动目的地。");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}finally{
//最后关闭线程池,但执行以前提交的任务,不接受新任务
threadPool.shutdown();
//关闭线程池,停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
//threadPool.shutdownNow();
}
}
}
//分布式工作线程
class Employee implements Runnable{
private CyclicBarrier barrier;
private int employeeIndex;
public Employee(CyclicBarrier barrier,int employeeIndex){
this.barrier = barrier;
this.employeeIndex = employeeIndex;
}
@Override
public void run() {
try {
System.out.println("员工:"+employeeIndex+",正在前往公司大门口集合...");
Thread.sleep(10*employeeIndex);
System.out.println("员工:"+employeeIndex+",已到达。");
barrier.await();
Thread.sleep(10);
System.out.println("员工:"+employeeIndex+",【自驾车】前往目的地");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
子线程执行了await()方法,必须等待其它所有子线程执行await()方法之后,才能一起继续后续的(await后main的)工作,就像上面的例子,所有自驾车必须都到达公司大门口之后,才能一起继续各自自驾车前往目的地。
但,主线程await()之后的工作与子线程await()之后的工作是不受影响的,只要所有的子线程执行了await()方法,主线程此时就可以后续的工作了,不必管子线程await()方法后续工作的情况。
可以看到的是只要都到达了屏障以后,各自执行自己,并不按照之前到达的顺序执行。
CountDownLatch
1、CountDownLatch:一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
2、ThreadPoolExecutor/ExecutorService:线程池,使用线程池可以复用线程,降低频繁创建线程造成的性能消耗,同时对线程的创建、启动、停止、销毁等操作更简便。
3、使用场景举例:
年末公司组织团建,要求每一位员工周六上午8点到公司门口集合,统一乘坐公司所租大巴前往目的地。
在这个案例中,公司作为主线程,员工作为子线程。
package com.test.thread;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author javaloveiphone
* @date 创建时间:2017年1月25日 上午10:59:11
* @Description:
*/
public class Company {
public static void main(String[] args) throws InterruptedException {
//员工数量
int count = 5;
//创建计数器
//构造参数传入的数量值代表的是latch.countDown()调用的次数
CountDownLatch latch = new CountDownLatch(count);
//创建线程池,可以通过以下方式创建
//ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1,1,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(count));
ExecutorService threadPool = Executors.newFixedThreadPool(count);
System.out.println("公司发送通知,每一位员工在周六早上8点到公司大门口集合");
for(int i =0;i<count ;i++){
//将子线程添加进线程池执行
Thread.sleep(10);
threadPool.execute(new Employee(latch,i+1));
}
try {
//阻塞当前线程,直到所有员工到达公司大门口之后才执行
latch.await();
// 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
//latch.await(long timeout, TimeUnit unit)
System.out.println("所有员工已经到达公司大门口,大巴车发动,前往活动目的地。");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//最后关闭线程池,但执行以前提交的任务,不接受新任务
threadPool.shutdown();
//关闭线程池,停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
//threadPool.shutdownNow();
}
}
}
//分布式工作线程
class Employee implements Runnable{
private CountDownLatch latch;
private int employeeIndex;
public Employee(CountDownLatch latch,int employeeIndex){
this.latch = latch;
this.employeeIndex = employeeIndex;
}
@Override
public void run() {
try {
System.out.println("员工:"+employeeIndex+",正在前往公司大门口集合...");
Thread.sleep(10);
System.out.println("员工:"+employeeIndex+",已到达。");
} catch (Exception e) {
e.printStackTrace();
}finally{
//当前计算工作已结束,计数器减一
latch.countDown();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行coutDown()之后,继续执行自己的工作,不受主线程的影响
System.out.println("员工:"+employeeIndex+",吃饭、喝水、拍照。");
}
}
}
每一个员工到达之后,执行countDown()方法,直到所有员工到达之后,计数器为0,主线程才会继续执行。
但子线程执行了countDown()方法,之后会继续自己的工作,比如上面的【吃饭、喝水、拍照】,是不受主线程是否阻塞、其它线程是否已经执行countDown()方法的影响的。
CyclicBarrier与CountDownLatch的区别:
1)、构造两者对象传入的参数不一样:构造CyclicBarrier比构造CountDownLatch的参数大了1,原因是构造CyclicBarrier的数量表示的是调用await()的次数,构造CountDownLatch的数量表示的是调用countDown()的次数;
2)、子线程调用了barrier.await()之后,必须等待所有子线程都完成barrier.await()调用后才能一起继续后续自己的工作,而子线程调用latch.countDown()之后,会继续子线程自己的工作,不用等待其它子线程latch.countDown()调用情况。
3)、CyclicBarrier可以循环使用,而CountDownLatch不是循环使用的。
通俗一点就是:
countDownLatch 类似于学校的 4*100接力赛;一共count为4次(4圈),当一个人跑完以后,他就可以休息,喝水了(继续执行子线程),其他人也一样,等所有人都跑完以后,count=0了,裁判宣布结果(main线程)。
CyclicBarrier:有一天同学们上课了,班里老师来的早,等待着那些同学陆陆续续的到来。老师讲课(main线程),同学们听课,记笔记(子线程)。同学们还没有来全,老师不能讲课(main线程被阻塞了),其他在教室的学生也不能记笔记,因为老师还没开始讲呢(子线程在屏障前等待),当所有人都到了以后,开始各干各的,老师讲课,他们在下边记笔记,传纸条等等等。互不打扰。
再说一下谷歌插件的ListeningExecutorService的使用方法
由于普通的线程池,返回的Future,功能比较单一;Guava 定义了 ListenableFuture接口并继承了JDK concurrent包下的Future 接口,ListenableFuture 允许你注册回调方法(callbacks),在运算(多线程执行)完成的时候进行调用。
1.使用方法如下:
1.创建线程池
2.装饰线程池
3.任务处理
4.回调函数处理
5.所有任务完成后处理
会执行回调方法, 执行成功的,执行不成功的都走方法
执行成功截图:
将等待失效时间修改如下:
线程池等待5秒,五秒后自动结束里面的线程。
执行了shutdownNow方法,正在执行的被中断了,未被执行的因为本例是基础的Callable所有没能接收返回的Runnable线程,线程继承Runnable可以接收返回的未被执行的那些线程。
上述案例的全部代码如下:
pom依赖
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.15</version>
</dependency>
</dependencies>
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
/**
* @author Shuyu.Wang
* @package:com.ganinfo.test
* @className:
* @description:
* @date 2018-10-28 19:35
**/
public class AuthCallable implements Callable {
private AuthType authType;
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
@Override
public Object call() throws Exception {
if ("1".equals(authType.getType())){
System.out.println("方式一"+authType.getName()+"授权start");
Thread.sleep(8000);
System.out.println("方式一授权end");
return authType.getName();
}
if ("2".equals(authType.getType())){
System.out.println("方式二"+authType.getName()+"授权start");
Thread.sleep(7000);
System.out.println("方式二授权end");
return authType.getName();
}
if ("3".equals(authType.getType())){
System.out.println("方式三"+authType.getName()+"授权start");
Thread.sleep(5000);
System.out.println("方式三授权end");
return authType.getName();
}
if ("4".equals(authType.getType())){
System.out.println("方式四"+authType.getName()+"授权start");
Thread.sleep(3000);
System.out.println("方式四授权end");
return authType.getName();
}
if ("5".equals(authType.getType())){
System.out.println("方式五"+authType.getName()+"授权start");
Thread.sleep(1000);
System.out.println("方式五授权end");
return authType.getName();
}
return null;
}
public void setAuthType(AuthType authType) {
this.authType = authType;
}
}
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* @author Shuyu.Wang
* @package:com.ganinfo.test
* @className:
* @description:
* @date 2018-10-28 19:41
**/
public class AuthService {
public static void main(String[] args) {
long start=System.currentTimeMillis();
final List<String> list = new ArrayList<>();
int count=5;
try {
final CountDownLatch countDownLatch = new CountDownLatch(count);
ExecutorService executorService = Executors.newFixedThreadPool(8);
ListeningExecutorService listeningExecutorService = MoreExecutors.listeningDecorator(executorService);
for (int i = 1; i <= count; i++) {
AuthCallable authCallable = new AuthCallable();
AuthType authType = new AuthType();
authType.setType(String.valueOf(i));
authType.setName(String.valueOf(i) + "名称");
authCallable.setAuthType(authType);
ListenableFuture listenableFuture = listeningExecutorService.submit(authCallable);
Futures.addCallback(listenableFuture, new FutureCallback<String>() {
@Override
public void onSuccess(String name) {
System.out.println("授权结果" + name);
list.add(name);
countDownLatch.countDown();
}
@Override
public void onFailure(Throwable throwable) {
countDownLatch.countDown();
System.out.println("处理出错:"+throwable);
}
});
}
try {
executorService.shutdown();
//shutdown调用后,不可以再submit新的task,已经submit的将继续执行。
if (!countDownLatch.await(5, TimeUnit.MINUTES)) {
System.out.println("超时的时候向线程池中所有的线程发出中断");
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
executorService.shutdownNow();
}
} catch (InterruptedException e) {
e.printStackTrace();
//shutdownNow试图停止当前正执行的task,并返回尚未执行的task的list
//由于本例中继承的是Callable,所以无法接收返回的Runable接口
List<Runnable> listRun =executorService.shutdownNow();
}
System.out.println("执行结果" + list.toString());
long end=System.currentTimeMillis();
System.out.println("用时" + (end-start));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
import lombok.Data;
/**
* @author Shuyu.Wang
* @package:com.ganinfo.test
* @className:
* @description:
* @date 2018-10-28 19:34
**/
@Data
public class AuthType {
private String type;
private String name;
}
一句话说明白shutdown和shutdownNow的区别
shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
参考文章:
Java并发编程–CountDownLatch配合线程池
java多线程CyclicBarrier使用示例,让线程起步走
java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例
https://blog.csdn.net/weixin_39800144/article/details/82776523