日常开发学习中,多线程的运用可谓是无处不在,JDK为我们多线程开发提供了一个工具CountDownLatch,可以很好的配合我们高效完成业务逻辑的开发。
CountDownLatch
概念
- CountDownLatch可以理解为一个线程在执行某个任务之前等待其他一个或多个线程完成自有任务的辅助类。
-
CountDownLatch内置了一个计数器,可以理解为任务数量,执行完某一项任务时,该计数-1,当执行完所有任务时,等待线程被唤醒并开始执行。
CountDownLatch.jpg
用法
- 构造方法
/*
*count 任务总数
*/
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
- 等待线程
CountDownLatch countDownLatch = new CountDownLatch(count);
countDownLatch.await();
- 执行任务的子线程
//业务逻辑 do something
if(isFinished){
countDownLatch.countDown();
}
举个例子
需求:用户在自己相册里选择多张照片上传到图片服务器,并将图片地址通过后台接口保存到数据库。
分析
假设用户此时选择了10张照片,我们定义3个异步线程来同时进行上传,当所有图片上传完成之后,调用接口,保存地址。实践
这里我运用打印的方式模拟上传步骤,重点在于让大家理解此类的用法。
public static void main(String[] args) {
LinkedList<File> uploadFiles = new LinkedList<>();
for (int i = 0;i<10;i++){
//假设有10张图片需要上传
uploadFiles.add(new File("picture"+i+".jpg"));
}
//需要上传的图片数量
CountDownLatch countDownLatch = new CountDownLatch(uploadFiles.size());
int threadCount = 3; //开启三个线程同时进行上传
for (int i = 0 ;i<threadCount;i++){
Thread thread = new Thread(new PhotoUploadTask(countDownLatch,uploadFiles));
thread.setName("Thread No."+i);
thread.start();
}
try {
countDownLatch.await();
System.out.println("所有图片全部上传完成,调用服务器API接口");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
注意看,当调用了countDownLatch.await()
此时当前线程会处于等待状态,这句代码以后的代码将不会马上执行,等待另外三个线程的任务执行完毕之后才会继续执行下去
try {
countDownLatch.await();
System.out.println("所有图片全部上传完成,调用服务器API接口");
} catch (InterruptedException e) {
e.printStackTrace();
}
这里为了方便,我将主线程当做了等待线程,小伙伴们实际开发中切莫效仿。
再来看看PhotoUploadTask里面的内容
public class PhotoUploadTask implements Runnable{
CountDownLatch countDownLatch;
LinkedList<File> upLoadFiles;
String threadName;
Random random;
int uploadTime;//完成任务的次数
public PhotoUploadTask(CountDownLatch countDownLatch, LinkedList<File> upLoadFiles){
this.countDownLatch = countDownLatch;
this.upLoadFiles = upLoadFiles;
random = new Random();
}
@Override
public void run() {
while (upLoadFiles.size()>0){
File file;
synchronized (upLoadFiles){
file = upLoadFiles.removeFirst();
}
threadName = Thread.currentThread().getName();
try {
//模拟上传图片 每张图大小不一样 耗时不一样
long timeCost = 1000+(random.nextInt(6)*500);
Thread.sleep(timeCost);
uploadTime++;
System.out.println(threadName+"-->第"+uploadTime+"次 图片上传完成"+file.getPath() +" 耗时"+timeCost);
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这其实就是一个Runnable的实现类,这里运用LinkedList来作为任务容器,每执行一个任务,我们就将这个任务从容器里移除,避免多线程的重复执行。
为了还原真实场景,我将每个图片上传的耗时做个区别,将线程名 和该线程执行了多少次上传任务,以及单次耗时做了打印
当线程完成了单次任务时,调用countDownLatch.countDown()
,这时计数器会对应减1,当计数器减到0时,等待线程将被唤醒
我们执行一下这段代码看看结果:
Thread No.1-->第1次 图片上传完成picture1.jpg 耗时2500
Thread No.0-->第1次 图片上传完成picture0.jpg 耗时2500
Thread No.2-->第1次 图片上传完成picture2.jpg 耗时3000
Thread No.1-->第2次 图片上传完成picture3.jpg 耗时2000
Thread No.2-->第2次 图片上传完成picture5.jpg 耗时2000
Thread No.0-->第2次 图片上传完成picture4.jpg 耗时3000
Thread No.1-->第3次 图片上传完成picture6.jpg 耗时3000
Thread No.2-->第3次 图片上传完成picture7.jpg 耗时3000
Thread No.0-->第3次 图片上传完成picture8.jpg 耗时3000
Thread No.1-->第4次 图片上传完成picture9.jpg 耗时3000
所有图片全部上传完成,调用服务器API接口
- 总结
可以看到三个线程执行的结果,线程NO.0 执行了3次任务,线程NO.1执行了4次任务,线程NO.2执行了3次任务,一共10次任务,并在所有图片上传任务结束后调用API接口。