概述
线程过多会带来调度开销,进而影响缓存局部性和整体性能;而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务,避免了在处理短时间任务时创建与销毁线程的代价;线程池不仅能够保证内核的充分利用,还能防止过分调度
设置线程池的最大线程数可参照:JUC_CPU密集型与I/O密集型_Mudrock__的博客-CSDN博客
创建线程池
方式一:通过Executors工具类
通过Executors可创建四种线程池,分别可创建固定大小的线程池,大小为一的线程池,大小可变的线程池,支持定时性、周期性任务的线程池
Executors.newFixedThreadPool(5); //固定大小的线程池
Executors.newSingleThreadExecutor();//大小为一的线程池
Executors.newCachedThreadPool(); //大小可变的线程池
Executors.newScheduledThreadPool(5);//支持定时性、周期性任务的线程池(参数为corePoolSize)
但不建议通过Executor创建线程池,原因如下:
使用Executors的弊端: FixedThreadPool和SingleThreadPool: 允许请求的队列长度为Integer.MAX_VALUE 可能会堆积大量的请求 导致OOM CachedThreadPool和ScheduledThreadPool: 允许创建的线程数量为Integer.MAX_VALUE 可能会创建大量的线程 导致OOM
方式二:通过ThreadPoolExecutor类
public ThreadPoolExecutor(int corePoolSize, //核心线程数(或者称最小线程数 线程池存在期间会持续开启的线程的数量)当线程数小于核心线程数时 即使此时存在闲置核心线程 线程池也会优先创建新线程处理新任务 int maximumPoolSize, //最大线程数 当请求的线程数大于corePoolSize+workQueue时 开启额外线程(但总线程数不超过maximumPoolSize) long keepAliveTime, //线程最长闲置时间(闲置时间超过阈值则关闭线程 直至线程数等于corePoolSize为止) TimeUnit unit, //最长闲置时间的单位 BlockingQueue<Runnable> workQueue, //线程阻塞队列 当请求的线程数大于corePoolSize时 线程进入BlockingQueue阻塞队列 ThreadFactory threadFactory, //线程工厂 用于创建一组任务相同的线程(一般不做改变) RejectedExecutionHandler handler) { //拒绝策略 当workQueue队列达到上限后 可执行拒绝策略(四种拒绝策略 一般使用默认策略AbortPolicy) 四种拒绝策略 AbortPolicy 抛出异常(默认拒绝策略) CallerRunsPolicy 让线程的调用者线程代为执行 DiscardPolicy 不抛出异常(同时也不处理任务) DiscardOldestPolicy 尝试和运行最久的线程竞争(不一定竞争得过 竞争不过就不被处理 也不抛出异常)
示例代码
public class TestThreadPoolExecutor {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
5,
20,
10,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0; i < 500; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPool.shutdown();
}
}
}
运行效果
可以看到,允许的最大线程数为20,此时若再有线程申请执行,且申请完成后程池中的20条线程均未执行完成,线程数将会超过20,此时会抛出异常,此即为AbortPolicy拒绝策略
若将AbortPolicy拒绝策略改成CallerRunsPolicy拒绝策略,当线程数超过允许的最大线程数时,超过部分的线程的任务将会由线程的调用者线程代为执行
可以看到main线程代为执行了超过部分的线程的任务(线程池中的线程是由main线程调用的,所以代为执行者为main线程)