ThreadPoolExecutor 线程池执行并行任务
前言
在jdk中Executors类中提供了诸如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程的方法,但是都具有一定的局限性,不灵活,且内部还是通过ThreadPoolExecutor来创建的,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,尽量规避由于不合理使用线程池而引发的风险。
使用场景
- 单批次的for循环去执行任务,在执行某些耗时操作的时候其效率很低,比如说定时任务。
- 并发场景下的某些操作,单线程执行效率理想的效果。
其他依赖
引用这个依赖主要是为了使用ThreadFactoryBuilder构造器给线程池命名,当然我们也可以自己去实现ThreadFactory。
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.5</version>
</dependency>
代码
import cn.hutool.core.thread.ThreadFactoryBuilder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;
/**
* @author ming
* @version 1.0.0
* @date 2020/8/12 15:01
**/
public class ThreadPoolTest {
private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
final static long START_TIME = System.currentTimeMillis();
/**
* 根据系统核心数来设置线程数: private static int corePoolSize = Runtime.getRuntime().availableProcessors();
* 核心线程数
*/
private static int corePoolSize = 100;
/**
* 最大线程数
*/
private static int maximumPoolSize = corePoolSize * 2;
/**
* 线程存活时间
*/
private static long keepAliveTime = 101L;
/**
* 阻塞队列大小
*/
private static final int CAPACITY = 1000;
/**
* 线程池命名
*/
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNamePrefix("my-work-pool-").build();
private static Random random = new Random();
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
keepAliveTime, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(CAPACITY),
namedThreadFactory,
(r, executor) -> userDefinedRejectionPolicy(r));
/**
* 拒绝策略;当任务太多来不及处理时,如何拒绝任务;
*
* @param r Runnable
*/
private static void userDefinedRejectionPolicy(Runnable r) {
//一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,
//这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。
//1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
//
//2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
//
//3、DiscardOldestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
//
//4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
System.out.println(r.toString() + "执行了拒绝策略");
// TODO: 2020/8/19 这里写拒绝策略的相关逻辑 ,在某些场景下我们应该尽量避免丢弃任务
}
public static void main(String[] args) throws InterruptedException {
System.out.println("--->> 准备开始执行任务 <<---");
// 初始30个任务(并行),如果处理器核心线程有这么多,那么久可以说的是并行的。
CountDownLatch latch = new CountDownLatch(30);
long start = System.currentTimeMillis();
// mission
Map<String, List<String>> map = new HashMap<>();
for (int i = 0; i < latch.getCount(); i++) {
int taskNum = 100;
List<String> list = new ArrayList<>();
for (int j = 0; j < taskNum; j++) {
list.add("sub_task-" + j);
}
map.put("mission_" + i, list);
}
// 执行任务
for (Map.Entry<String, List<String>> entry : map.entrySet()) {
executor.execute(new Worker(latch, "执行自定义任务", 1600 + random.nextInt(100), entry.getKey(), entry.getValue()));
}
executor.shutdown();
// 等待所有任务结束
latch.await();
long end = System.currentTimeMillis();
System.out.println("任务执行累计耗时:" + figureOutTimeInterval(end - start));
}
private static String figureOutTimeInterval(long interval) {
//初始化Formatter的转换格式。
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
formatter.setTimeZone(TimeZone.getTimeZone("GMT+00:00"));
String hms = formatter.format(interval);
System.out.println("耗时 == " + hms);
return hms;
}
/**
* 具体的任务逻辑
*/
static class Worker implements Runnable {
private CountDownLatch latch;
private String name;
private Integer runTime;
private List<String> list;
private String task;
Worker(CountDownLatch latch, String name, Integer runTime, String task, List<String> list) {
this.latch = latch;
this.name = name;
this.runTime = runTime;
this.task = task;
this.list = list;
}
@Override
public void run() {
try {
System.out.println(name + " do Working begin at " + SDF.format(new Date(START_TIME)));
for (String item : list) {
System.out.println(name + " -- " + task + " and " + item);
//模拟耗时任务
Thread.sleep(runTime);
}
long end = System.currentTimeMillis();
figureOutTimeInterval(end - START_TIME);
System.out.println(name + " do Working complete at " + SDF.format(new Date(end)));
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: 2020/8/19 这里做失败处理
} finally {
//单次任务结束,计数器减一
latch.countDown();
}
}
}
}