logo头像
Snippet 博客主题

线程池容易被忽略的地方

本文于 872 天之前发表,文中内容可能已经过时。

为什么用线程池?

  • 节约省资源
    创建线程有损耗,销毁线程也有损耗
  • 执行效率高
    充分利用多核CPU,并行处理。处理任务,不需要等待线程创建的过程立即执行。
  • 管理线程
    线程是操作系统稀缺的资源,不可滥用。线程池可以做到统一分配,调优和监控。

线程池的实现原理

任务请求过来,当前线程数小于核心线程池线程数,创建线程执行;
当前线程数大于等于核心线程数,则放到队列等待空闲线程处理。
当任务队列满了,则创建线程,执行任务。
当线程池的最大线程数都繁忙,则走拒绝策略。

注意

这里有个核心线程池,与线程池的概念
核心线程池的线程没有初始化创建完,而不会用核心线程池空闲线程执行新来的任务,而是优先创建线程

思考

当线程池当前线程数已经大于核心线程数,这时候线程池也已经空闲了一段时候,非核心线程已经消亡,
这个时候新请求过来一个任务,这时候线程池是新建一个线程处理,还是将线程放入队列中,
又或者用核心线程去处理

如何优雅关闭线程池

shutdown与shutdownNow,都无法保证100%关闭线程池的线程
shutdownNow将所有工作线程中断,并返回还没完成的任务集
isShutDown() 当执行了上面二个命令,则返回true
isTerminaed() 判断线程是否全部终止,线程池完美关闭。

如何合理配置线程池

根据任务的特性分几个方面:

  • 任务的性质
    IO密集的任务,2Ncpu 个线程数
    CPU计算密集的任务,Ncpu+1 个线程数
    混合型的任务,则看情况,将任务分成IO密集和CPU密集的分别执行,如果二者执行时间相差不大,则没必要。

  • 任务的执行的时间长短
    让执行时间短的先行

  • 任务的优先级
    使用优先级队列,注意会导致优先级不高的永远不执行。
  • 任务的依赖性
    是否依赖其他资源,比如数据库,Redis,第三方。2Ncpu 个线程数执行。

线程池监控

可以利用线程池里面提供的参数监控:

/**
 * 线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过
 */
private int largestPoolSize;

/**
 * 线程池在运行过程中已完成的任务数量,小于或等于taskCount
 */
private long completedTaskCount;

/**
 * 线程池需要执行的任务数量
 */
public long getTaskCount() {}

/**
 * 线程池的线程数量。如果线程池不销毁的话,
 * 线程池里的线程不会自动销毁,所以这个大小只增不减
 */
public int getPoolSize() {}

重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。

思考:getPoolSize()
线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。
这句话,没那么绝对。线程池中工作线程销毁的条件:

1)  参数allowCoreThreadTimeOut为true
2)  该线程在keepAliveTime时间内获取不到任务,即空闲这么长时间
3)  当前线程池大小 > 核心线程池大小corePoolSize

long keepAliveTime
该线程池中非核心线程闲置超时时长

一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
另外默认对非核心线程有效,若想核心线程也适用于这个机制,
可以调用allowCoreThreadTimeOut()方法。这样的话就没有核心线程这一说了。

线程池的队列

ArrayBlockingQueue数组有界队列
LinkedBlockingQueue链表无界队列 Executors.newFixedThreadPool()
PriorityBlockingQueue优先级无界队列
SynchronousQueue不保留任务的队列 Executors.newCachedThreadPool
DelayQueue 延迟队列

附录

你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识
深入理解线程池原理篇