在项目中,线程是一种稀缺资源,频繁的创建和销毁,对于系统的性能有着很大的消耗。线程池是线程资源复用的典范之作,通过维护一个一定数量的线程集合,在需要运行线程任务的时候直接从这个集合中取出一个线程去运行任务,而不是重新创建一个。
在Java中提供了几种线程池创建的方法,不过这几种方法都是最后通过 ThreadPoolExecutor
来创建线程的。在开始讲Java提供的几种之前,先讲下ThreadPoolExecutor
中几个关键字的含义。
ThreadPoolExecutor
内部工作原理
- corePoolSize :池中所保存的线程数,包括空闲线程
- maximumPoolSize:池中允许的最大线程数
- keepAliveTime: 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间
- unit:keepAliveTime 参数的时间单位
- workQueue :执行前用于保持任务的队列。此队列仅保持由 execute方法提交的 Runnable任务
- threadFactory:执行程序创建新线程时使用的工厂
- handler :由于超出线程范围和队列容量而使执行
线程池运行思路
- 如果当前池大小 poolSize 小于 corePoolSize ,则创建新线程执行任务
- 如果当前池大小 poolSize 大于 corePoolSize ,且等待队列未满,则进入等待队列
- 如果当前池大小 poolSize 大于 corePoolSize 且小于 maximumPoolSize ,且等待队列已满,则创建新线程执行任务
- 如果当前池大小 poolSize 大于 corePoolSize 且大于 maximumPoolSize ,且等待队列已满,则调用拒绝策略来处理该任务
- 线程池里的每个线程执行完任务后不会立刻退出,而是会去检查下等待队列里是否还有线程任务需要执行,如果在 keepAliveTime 里等不到新的任务了,那么线程就会退出
拒绝策略
线程池有四种拒绝策略: - AbortPolicy:抛出异常,默认
- CallerRunsPolicy:不使用线程池执行
- DiscardPolicy:直接丢弃任务
- DiscardOldestPolicy:丢弃队列中最旧的任务
对于线程池选择的拒绝策略可以通过RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
来设置。源码
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
Java中提供的几种方法
newFixedThreadPool
通过创建一个corePoolSize
和maximumPoolSize
相同的线程池。使用LinkedBlockingQuene作为阻塞队列,不过当线程池没有可执行任务时,也不会释放线程。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
1、初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE,即2147483647,内部使用SynchronousQueue作为阻塞队列;
2、和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,
当线程的空闲时间超过keepAliveTime,会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销;
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newSingleThreadExecutor
初始化的线程池中只有一个线程,如果该线程异常结束,会重新创建一个新的线程继续执行任务,唯一的线程可以保证所提交任务的顺序执行,内部使用LinkedBlockingQueue作为阻塞队列。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
newScheduledThreadPool
初始化的线程池可以在指定的时间内周期性的执行所提交的任务,在实际的业务场景中可以使用该线程池定期的同步数据。除了newScheduledThreadPool的内部实现特殊一点之外,其它几个线程池都是基于ThreadPoolExecutor类实现的.
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
注意
在阿里Java开发手册上面关于线程池创建的注意: