一、需求描述
利用线程池实现多线程分批执行任务。
例如:线程池容量为10,我有20个任务,第一次利用线程池中的10个线程异步执行完10个任务。10个任务完成后,不管任务成功或者失败,线程池回收10个线程继续完成剩下10个任务。
实际应用:当我们批量的需求比如启动1000个节点,启动一个节点的时间大概是3s,我们肯定不会去并行执行1000次启动,肯定是利用线程池的技术利用多线去异步启动1000个节点,但是我们又不可能给线程池1000个线程去准备,只能采用循环分批的思想去执行1000次启动。
二、代码示例
主函数:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.google.common.collect.Lists;
public class BatchTest2 {
public static void main(String[] args) throws InterruptedException {
//线程池10个线程
ExecutorService executorService = Executors.newFixedThreadPool(10);
//第一批十个任务
List<StartAgent> agentsStart = Lists.newArrayList(new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent());
//第二批十个任务
List<StartAgent> agentsStart2 = Lists.newArrayList(new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent(),new StartAgent());
List<List<StartAgent>> task = new ArrayList<>();
task.add(agentsStart);
task.add(agentsStart2);
//记录任务执行时间
long t1 = System.currentTimeMillis();
CountDownLatch c ;
//循环任务组
for(List<StartAgent> startList : task){
//定义线程阻塞为10
c = new CountDownLatch(10);
for(StartAgent agent : startList){
agent.setCountDownLatch(c);
executorService.submit(agent);
}
//阻塞,等待十个任务都执行后,才继续下一批10任务
c.await();
}
long t2 = System.currentTimeMillis();
System.out.println("执行时间:"+ (t2 - t1)/1000);
executorService.shutdown();
}
}
StartAgent.java
import java.util.concurrent.CountDownLatch;
public class StartAgent implements Runnable {
private CountDownLatch countDownLatch;
@Override
public void run() {
try {
System.out.println("开始启动节点:" + Thread.currentThread().getName());
//模拟每个任务执行3秒钟
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "执行完毕");
// 模拟执行任务异常
// if(Thread.currentThread().getName().equals("pool-1-thread-4")){
// System.out.println();
// throw new InterruptedException("pool-1-thread-4启动失败");
// }
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//注意一定要在finally调用countDown,否则产生异常导致没调用到countDown造成程序死锁
countDownLatch.countDown();
}
}
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
}
三、调用结果
开始启动节点:pool-1-thread-1
开始启动节点:pool-1-thread-3
开始启动节点:pool-1-thread-5
开始启动节点:pool-1-thread-2
开始启动节点:pool-1-thread-6
开始启动节点:pool-1-thread-7
开始启动节点:pool-1-thread-4
开始启动节点:pool-1-thread-8
开始启动节点:pool-1-thread-9
开始启动节点:pool-1-thread-10
pool-1-thread-3执行完毕
pool-1-thread-1执行完毕
pool-1-thread-8执行完毕
pool-1-thread-9执行完毕
pool-1-thread-2执行完毕
pool-1-thread-5执行完毕
pool-1-thread-7执行完毕
pool-1-thread-4执行完毕
pool-1-thread-10执行完毕
pool-1-thread-6执行完毕
开始启动节点:pool-1-thread-3
开始启动节点:pool-1-thread-9
开始启动节点:pool-1-thread-2
开始启动节点:pool-1-thread-8
开始启动节点:pool-1-thread-1
开始启动节点:pool-1-thread-7
开始启动节点:pool-1-thread-10
开始启动节点:pool-1-thread-6
开始启动节点:pool-1-thread-5
开始启动节点:pool-1-thread-4
pool-1-thread-9执行完毕
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-8执行完毕
pool-1-thread-7执行完毕
pool-1-thread-1执行完毕
pool-1-thread-10执行完毕
pool-1-thread-5执行完毕
pool-1-thread-6执行完毕
pool-1-thread-4执行完毕
执行时间:6
可以看到,线程池先执行完第一批任务后,才执行第二批任务,而且每批任务都是异步执行,只用了3s,两批任务只用了6s。
以上只是一个简单的demo,提供一个思路,主要用到的知识点是CountDownLatch,网上对这个知识点介绍比较多了,我就不啰嗦了。