📢博客主页:折戏花
📢欢迎点赞 👍 收藏 ?留言 📝 如有错误敬请指正!
📢本文由折戏花编写,首发于CSDN🙉
本文主要简单介绍一下线程池的创建使用
线程池的创建?法可分为 2 类:
一种通过 ThreadPoolExecutor 创建的线程池;
一种通过 Executors 创建的线程池。
线程池的创建?式总共有 7 种(其中 6 种是通过 Executors 创建的, 1 种是通过ThreadPoolExecutor 创建的):
1、Executors.newFixedThreadPool:创建?个固定??的线程池,可控制并发的线程数,超出的线程会在队列中等待;
2、Executors.newCachedThreadPool:创建?个可缓存的线程池,若线程数超过处理所需,缓存?段时间后会回收,若线程数不够,则新建线程;
3、Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执?顺序;
4、Executors.newScheduledThreadPool:创建?个可以执?延迟任务的线程池;
5、Executors.newSingleThreadScheduledExecutor:创建?个单线程的可以执?延迟任务的线程池;
6、Executors.newWorkStealingPool:创建?个抢占式执?的线程池(任务执?顺序不确定)【JDK1.8 新增】。
7、ThreadPoolExecutor:最原始的创建线程池的?式。也是本文主要介绍的线程池创建方式
阿里巴巴的《Java开发手册》已经像我们说明了线程池创建方式的选择:
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
通过看源码我们可以知道ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口
通过ThreadPoolExecutor的构造函数我们可以看到ThreadPoolExecutor包含了7个核心参数,我们介绍下这些参数的含义
参数含义:
1、corePoolSize:核心线程池的大小,即线程池中始终存活的线程数
2、maximumPoolSize:最大线程池的大小,即最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数
3、keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程
4、unit:keepAliveTime的时间单位,有以下7种
TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒
5、workQueue:用来暂时保存等待执行的任务的工作队列,是一个阻塞队列,均为线程安全,它包含以下 7 种类型:较常用的是 LinkedBlockingQueue 和 Synchronous,线程池的排队策略与 BlockingQueue 有关,有以下7种队列
ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
6、threadFactory:线程工厂提供线程的创建方式,默认使用Executors.defaultThreadFactory()
7、handler:当线程池所处理的任务数超过其承载容量或关闭后继续有任务提交时,所调用的拒绝策略,提供了4种拒绝策略,默认策略为 AbortPolicy
AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。
import java.util.concurrent.*;
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
// 创建一个线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
1, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(5), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
// 提交任务给线程池
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println("Task " + taskId + " is running.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Task " + taskId + " is completed.");
});
}
// 关闭线程池
executor.shutdown();
}
执行结果
从结果中我们可以看到队列大小小于线程任务时,我们的线程池则会创建线程数刚好为maximumPoolSize的值
除了上述的4种拒绝策略,我们还可以通过实现RejectedExecutionHandler 接口来自定义拒绝策略
public class CustomRejectionPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//这是自定义策略
}
}
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("当前任务被执行线程:" + Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(1,
1,
100,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1),
new CustomRejectionPolicy());
// 添加并执行任务
executor.execute(runnable);
executor.execute(runnable);
executor.execute(runnable);
executor.execute(runnable);
// 关闭线程池
executor.shutdown();
}
执行结果