使用线程池的目的
-
线程是稀缺资源,不能频繁的创建
-
解耦作用:线程的创建与执行完全分开,方便维护
-
将其放入一个池子中,可以给其他任务进行复用
优点
-
降低资源消耗,通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。
-
提高相应速度,当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-
提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅仅会消耗系统资源,还会降低体统的稳定性,使用线程可以进行统一分配,调优和监控
创建线程池的方式
在 JDK 1.5 之后推出了相关的 api,常见的创建线程池方式有以下三种:
-
Executors.newCachedThreadPool():无限线程池,线程池动态扩容
-
Executors.newFixedThreadPool(nThreads):创建固定大小的线程池
-
Executors.newSingleThreadExecutor():创建单个线程的线程池
-
Executors.newScheduledThreadPool():定时执行的线程池
JDK1.8新增
- Executors.newWorkStealingPool(): newWorkStealingPool适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中
但是在阿里巴巴开发规约中对线程池的创建有以下几个强制要求:
可以看到规约是不允许使用Executors直接创建线程池的,而要通过ThreadPoolExecutor的方式创建
1 public static ExecutorService newCachedThreadPool() { 2 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 3 60L, TimeUnit.SECONDS, 4 new SynchronousQueue<Runnable>()); 5 }
我们看到这三种方式的源码也是通过ThreadPoolExecutor的方式来创建的,接下来我们看一下ThreadPoolExecutor的API
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
这几个核心参数的作用:
-
corePoolSize
核心线程池大小 -
maximumPoolSize
为线程池最大线程大小 -
keepAliveTime
线程池中超过 corePoolSize 数目的空闲线程最大存活时间 - unit keepAliveTime的时间单位
-
workQueue
用于存放任务的阻塞队列 - threadFactory 线程工厂,可以自定义线程名称
-
handler
当队列和最大线程池都满了之后的拒绝策略- AbortPolicy:处理程序遭到拒绝将抛出运行时 RejectedExecutionException
- CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度
- DiscardPolicy:不能执行的任务将被删除
- DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
工作原理
-
当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程
-
当线程池达到corePoolSize时,新提交任务将被放入 workQueue 中,等待线程池中任务调度执行
-
当workQueue已满,且 maximumPoolSize 大于 corePoolSize 时,新提交任务会创建新线程执行任务
-
当提交任务数超过 maximumPoolSize 时,新提交任务由 RejectedExecutionHandler 处理
-
当线程池中超过corePoolSize 线程,空闲时间达到 keepAliveTime 时,关闭空闲线程
-
当设置allowCoreThreadTimeOut(true) 时,线程池中 corePoolSize 线程空闲时间达到 keepAliveTime 也将关闭
线程池大小创建的原则
-
IO 密集型任务:由于线程并不是一直在运行,所以可以尽可能的多配置线程,比如 CPU 个数 * 2
-
CPU 密集型任务(大量复杂的运算)应当分配较少的线程,比如 CPU 个数相当的大小