一、使用线程池的好处?
1、降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2、提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
3、提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性。
二、创建线程池的方式
2.1 Executors
- newSingleThreadPoll() 创建一个只有一个线程的线程池。
- newFixedThreadPool(int numOfThreads)来创建固定线程数的线程池。
- newCachedThreadPool()创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
2.2 ThreadPoolExecutor(推荐使用)
通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
2.3 使用Executors 创建线程池对象的弊端
FixedThreadPool 和 SingleThread Pool允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool允许的创建线程数量为 Integer.MAX_VALUE 可能会创建大量的线程,从而导致 OOM。
三、使用 ThreadPoolExecutor 创建的 7大参数
1、corePoolSize :线程池核心线程个数。
2、workQueue :用于保存等待执行的任务的阻塞队列,比如基于数组的有界ArrayBlockingQueue、基于链表的无界LinkedBlockingQueue、最多只有一个元素的同步队列SynchronousQueue 等。
3、omaximunPoolSize : 线程池最大线程数量。
4、ThreadFactory :创建线程的工厂,一般使用默认的即可。
5、RejectedExecutionHandler :饱和策略, 当队列满并且线程个数达到maximunPoolSize后采取的策略。
- AbortPolicy(抛出异常)
- CallerRunsPolicy(使用调用者所在线程来运行任务)
- DiscardOldestPolicy(调用poll 丢弃一个队列中等待最久的任务,执行当前任务)
- DiscardPolicy(默默丢弃,不抛出异常)
6、keeyAliveTime :存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置状态, 则这些闲置的线程能存活的最大时间。
7、TimeUnit : 存活时间的时间单位。
四、底层工作原理
1 使用核心线程进行操作。
2 核心线程全被占用,添加任务到阻塞队列。
3 阻塞队列已满,增加线程池中的线程数。
4 阻塞队列已满,线程数全被占用,执行拒绝策略。
五、线程池配置合理线程数
-
CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
-
IO密集型任务
可以使用稍大的线程池,参考公式:CPU核数/ 1-阻塞系数。 阻塞系数为0.8~0.9之间 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
六、线程池的项目使用示例(七大参数需要根据项目的场景来不同定义):
@Bean
public ThreadPoolExecutor demoThreadPool() {
SynchronousQueue<Runnable> workQueue = new SynchronousQueue<>();
ThreadFactory threadFactory = new ThreadFactory() {
private AtomicInteger threadNumber = new AtomicInteger(0);
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "demo" + threadNumber.incrementAndGet());
}
};
return new ThreadPoolExecutor(
1, 50, 180,
TimeUnit.SECONDS,
workQueue,
threadFactory,
(r, executor) -> log.error("**"));
}