类介绍
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。
一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行
使用场景
在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 这个时候就可以使用CountDownLatch。CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
方法说明
countDown()方法
public void countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。
await方法
public boolean await(long timeout,TimeUnit unit) throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则此方法立刻返回 true 值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
* 由于调用 countDown() 方法,计数到达零;或者
* 其他某个线程中断当前线程;或者
* 已超出指定的等待时间。
如果计数到达零,则该方法返回 true 值。
如果当前线程:
* 在进入此方法时已经设置了该线程的中断状态;或者
* 在等待时被中断,
则抛出 InterruptedException,并且清除当前线程的已中断状态。如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。
参数:
timeout - 要等待的最长时间
unit - timeout 参数的时间单位。
返回:
如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false
抛出:
InterruptedException - 如果当前线程在等待时被中断
例1:模拟100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。(十个人都到达终点时才结束)
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
// 开始的倒数锁 从1开始倒计时 减1就到0 开始赛跑
CountDownLatch begin = new CountDownLatch(1);
// 结束的倒数锁 从10开始倒计时 每一个人到达终点将减1 到0时全跑完 才比赛结束
CountDownLatch end = new CountDownLatch(10);
// 十名选手
ExecutorService exec = Executors.newFixedThreadPool(10);
//for循环 代表实名选手准备就绪 begin.countDown()未执行 所有的线程都不会执行
for (int index = 0; index < 10; index++) {
exec.execute(new MyRunnable(index, begin,end));
}
System.out.println("Game Start");
// begin减一 变为0,开始游戏
begin.countDown();
// 等待end变为0时,代表所有的选手都跑完
end.await();
System.out.println("Game Over");
exec.shutdown();
}
}
public class MyRunnable implements Runnable{
int no;//队员编号
//同一把锁
public static Object lock = new Object();
//开始倒计时的计数器 从1开始倒计时 减去1到0 立即向下执行 游戏开始
public CountDownLatch begin;
//结束倒计时的计数器 10个人 从10开始倒计时 每当有一个人到达终点时计数器减1 直到减到0 end.await(); 开始向下执行
public CountDownLatch end;
public MyRunnable(int no, CountDownLatch begin, CountDownLatch end) {
this.no = no;
this.begin = begin;
this.end = end;
}
@Override
public void run() {
try {
// 如果当前计数为零,则此方法立即向下执行
begin.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + no + " arrived");
} catch (InterruptedException e) {
} finally {
// 每个选手到达终点时,end就减一
synchronized (lock){
end.countDown();
}
}
}
}
输出结果
Game Start
No.9 arrived
No.6 arrived
No.8 arrived
No.7 arrived
No.10 arrived
No.1 arrived
No.5 arrived
No.4 arrived
No.2 arrived
No.3 arrived
Game Over
例2:根据多个图片的url地址实现多个图片的多线程上传全都上传成功后执行后续程序(但是后来发现用的Callable,在进行FutureTask.get()获取结果的时候线程阻塞,所以并不是没有实现多线程上传,但是有提供解决办法)
/***根据url上传图片
* @param urls 远程图片地址列表
* @param fileStoryType 上传位置
* @value FileUploadType.TEMP 上传的临时文件夹
FileUploadType.NO_TEMP 上传到正式文件夹
FileUploadType.GRAP_TEMP 上传到图片抓取文件夹
*@return 返回上传的状态 里面包含原上传的url地址 页面展示的url地址等
*/
public static List<FtpUploadState> uploadFileByUrls(List<String> urls, Short fileStoryType) {
List<FtpUploadState> ftpUploadStateList = null;
if(urls != null && urls.size() > 0){
ftpUploadStateList = new ArrayList<FtpUploadState>();
ExecutorService exec = Executors.newFixedThreadPool(urls.size());
CountDownLatch latch = new CountDownLatch(urls.size()); //计数器的数量 上传多少图片数量就会是多少 全部上传完成才会执行下一步
for (String romoteUrl : urls) {
UploadFileCallable uft = new UploadFileCallable(romoteUrl,FileUploadType.GRAP_TEMP, latch);
Future<FtpUploadState> ftpUploadStateFuture = exec.submit(uft); //执行线程并且返回结果
try {
ftpUploadStateList.add(ftpUploadStateFuture.get());
//取出call线程执行完毕后的返回结果
//ftpUploadStateFuture.get() 会阻塞主线程,当线程执行完才继续主线程 所以此处多线程上传文件并不是真正的多线程 而是一个线程一个线程的执行 具体原因详看下面的示例
//解决办法:将ftpUploadStateFuture放到list中在for循环外面去循环list去ftpUploadStateFuture.get() 而不是在for循环里面get
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
try {
latch.await(); //只要latch不为0就会一直等待 不会执行程序的下一步(所有图片上传完成后才执行下一步)
} catch (InterruptedException e) {
e.printStackTrace();
}
exec.shutdown();
}
return ftpUploadStateList;
}
class UploadFileCallable implements Callable {
static final Logger logger = LoggerFactory.getLogger(UploadFileCallable.class);
private Short fileType;
private String filePathFormat;
private String fileName;
private String urlImg;
private FtpUploadState ftpUploadState;
private CountDownLatch latchCount;
public UploadFileCallable() {}
public UploadFileCallable(String urlImg,Short fileType, CountDownLatch latchCount) {
super();
this.fileType = fileType;
this.latchCount = latchCount;
this.urlImg = urlImg;
}
public UploadFileCallable(Short fileType, String filePathFormat, String fileName,CountDownLatch latchCount) {
super();
this.fileType = fileType;
this.filePathFormat = filePathFormat;
this.fileName = fileName;
this.latchCount = latchCount;
}
//上传文件的线程
public FtpUploadState call() throws Exception {
FtpUploadState ftpUploadState = null;
int i =1;
try {
while (i <= 3) {
ftpUploadState = UploadFileUtil.uploadFileByUrl(urlImg, fileType);
if(ftpUploadState.isState()){
break;
}else {
logger.info("第"+i+"次抓取图片,mark{}",urlImg);
i++;
}
}
}catch(Exception e){
e.printStackTrace();
}
latchCount.countDown(); //每当上传完成一个图片 计数器减一 上传完成为0 主程序latch.await()出可以执行下面的代码
return ftpUploadState;
}
注意:下面举例说明 例2并不是多线程去上传文件,并提供相应的解决办法
方法体执行从1到4100 、4200 、4300 、4400 、4500 、4600 、4700 、4800 、4900 、5000的和 并返回值
1、单线程循环十次执行某个方法记录耗费时间
public class TestSigle {
public static void main(String[] args) throws Exception {
Long start = System.currentTimeMillis();
for (int j = 41; j < 51; j++) {
System.out.println("主程序循环体内************************");
//循环执行的方法体
int sum = 0;
for (int i = 0; i <= j*100; i++) {
Thread.sleep(1);
sum += i;
}
System.out.println("单线程 sum:sum"+sum);
}
Long end = System.currentTimeMillis();
System.out.println("耗费时间:" + String.valueOf((end - start) / 1000) + "秒");
System.out.println("主程序最后结束------------------------------------");
}
}
/*
*
输出结果:依次有序循环执行
主程序循环体内************************
单线程 sum:8407050
主程序循环体内************************
单线程 sum:8822100
主程序循环体内************************
单线程 sum:9247150
主程序循环体内************************
单线程 sum:9682200
主程序循环体内************************
单线程 sum:10127250
主程序循环体内************************
单线程 sum:10582300
主程序循环体内************************
单线程 sum:11047350
主程序循环体内************************
单线程 sum:11522400
主程序循环体内************************
单线程 sum:12007450
主程序循环体内************************
单线程 sum:12502500
耗费时间:82秒
主程序最后结束------------------------------------
*/
2、采用Callable接口结合线程池获取返回值(由于每执行一次就去获取会阻塞主线程)
public class TestCallable {
public static void main(String[] args) throws Exception {
ExecutorService exec = newFixedThreadPool(10);
Long start = System.currentTimeMillis();
for (int j = 41; j < 51; j++) {
Future<Integer> future = exec.submit(new ThreadDemo(j*100));
Integer sum = future.get(); //FutureTask 可用于 闭锁 单个线程不执行完主程序不往下执行
System.out.println(sum);
System.out.println("主程序循环体内************************");
}
Long end = System.currentTimeMillis();
System.out.println("耗费时间:" + String.valueOf((end - start) / 1000) + "秒");
exec.shutdown();
System.out.println("主程序最后结束------------------------------------");
}
}
class ThreadDemo implements Callable<Integer>{
private Integer num;
public ThreadDemo(Integer num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= num; i++) {
Thread.sleep(1);
sum += i;
}
System.out.println("Callable.call() sum---->:" + sum);
return sum;
}
}
/**
输出结果:在for循环体内执行 FutureTask.get()阻塞了主线程将任务塞进线程池
Callable.call() sum---->:8407050
主程序循环体内************************
Callable.call() sum---->:8822100
主程序循环体内************************
Callable.call() sum---->:9247150
主程序循环体内************************
Callable.call() sum---->:9682200
主程序循环体内************************
Callable.call() sum---->:10127250
主程序循环体内************************
Callable.call() sum---->:10582300
主程序循环体内************************
Callable.call() sum---->:11047350
主程序循环体内************************
Callable.call() sum---->:11522400
主程序循环体内************************
Callable.call() sum---->:12007450
主程序循环体内************************
Callable.call() sum---->:12502500
主程序循环体内************************
耗费时间:82秒
主程序最后结束------------------------------------
*/
3、自己将Furure放进List等主线程将多个线程都塞进其他线程里再去获取遍历结果
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
List<Future<Integer>> list = new ArrayList<>();
ExecutorService exec = newFixedThreadPool(10);
Long start = System.currentTimeMillis();
for(int j = 41;j < 51;j++){
Future<Integer> future = exec.submit(new ThreadDemo(j*100));
list.add(future);
System.out.println("主程序循环体内************************");
}
System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");
//将list里的FutureTask获取出来,调用get方法获取返回的值
list.stream().forEach(future -> {
try {
Integer sum = future.get();
System.out.println("future.get() sum:"+sum);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e1) {
e1.printStackTrace();
}
});
Long end = System.currentTimeMillis();
System.out.println("耗费时间(有循环遍历的时间):"+String.valueOf((end - start) / 1000)+"秒");
exec.shutdown();
System.out.println("主程序最后结束------------------------------------");
}
}
class ThreadDemo implements Callable<Integer>{
private Integer num;
public ThreadDemo(Integer num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= num; i++) {
Thread.sleep(1);
sum += i;
}
System.out.println("Callable.call() " + num +" sum---->:"+ sum);
return sum;
}
}
/**
输出结果:没有阻塞主线程将任务塞到线程池内
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
Callable.call() 4200 sum---->:8822100
Callable.call() 4300 sum---->:9247150
Callable.call() 4400 sum---->:9682200
Callable.call() 4500 sum---->:10127250
Callable.call() 4600 sum---->:10582300
Callable.call() 4100 sum---->:8407050
future.get() sum:8407050
future.get() sum:8822100
future.get() sum:9247150
future.get() sum:9682200
future.get() sum:10127250
future.get() sum:10582300
Callable.call() 4700 sum---->:11047350
future.get() sum:11047350
Callable.call() 4800 sum---->:11522400
future.get() sum:11522400
Callable.call() 4900 sum---->:12007450
future.get() sum:12007450
Callable.call() 5000 sum---->:12502500
future.get() sum:12502500
耗费时间(有循环遍历的时间):9秒
主程序最后结束------------------------------------
*/
4、利用FutureTask
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
FutureTask执行多任务计算的使用场景,利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务集合
List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();
ExecutorService exec = newFixedThreadPool(10);
Long start = System.currentTimeMillis();
for (int j = 41; j < 51; j++) {
// 传入Callable对象创建FutureTask对象
FutureTask<Integer> ft = new FutureTask<Integer>(new ThreadDemo(j * 100));
taskList.add(ft);
exec.submit(ft);
System.out.println("主程序循环体内************************");
}
System.out.println("所有计算任务提交完毕, 主线程接着干其他事情!");
//将list里的FutureTask获取出来,调用get方法获取返回的值
taskList.stream().forEach(future -> {
try {
Integer sum = future.get();
System.out.println("future.get() sum:" + sum);
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (ExecutionException e1) {
e1.printStackTrace();
}
});
Long end = System.currentTimeMillis();
System.out.println("耗费时间(有循环遍历的时间):" + String.valueOf((end - start) / 1000) + "秒");
exec.shutdown();
System.out.println("主程序最后结束------------------------------------");
}
}
class ThreadDemo implements Callable<Integer> {
private Integer num;
public ThreadDemo(Integer num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= num; i++) {
Thread.sleep(1);
sum += i;
}
System.out.println("Callable.call() " + num + " sum---->:" + sum);
return sum;
}
}
/**
输出结果:没有阻塞主线程将任务塞到线程池内
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
主程序循环体内************************
Callable.call() 4200 sum---->:8822100
Callable.call() 4300 sum---->:9247150
Callable.call() 4400 sum---->:9682200
Callable.call() 4500 sum---->:10127250
Callable.call() 4600 sum---->:10582300
Callable.call() 4100 sum---->:8407050
future.get() sum:8407050
future.get() sum:8822100
future.get() sum:9247150
future.get() sum:9682200
future.get() sum:10127250
future.get() sum:10582300
Callable.call() 4700 sum---->:11047350
future.get() sum:11047350
Callable.call() 4800 sum---->:11522400
future.get() sum:11522400
Callable.call() 4900 sum---->:12007450
future.get() sum:12007450
Callable.call() 5000 sum---->:12502500
future.get() sum:12502500
耗费时间(有循环遍历的时间):9秒
主程序最后结束------------------------------------
*/