线程池实现原理

270 阅读4分钟

线程池解决了两个不同问题:一是为异步任务执行提供更好的性能,减少任务调用的开销;二是提供了约束和管理资源的方法。

1、ThreadPoolExecutor线程池

JDK1.5引入了ThreadPoolExecutor类实现线程池原理,主要的构造函数为:

/**
 * @param corePoolSize 核心线程数
 * @param maximumPoolSize 最大线程数
 * @param keepAliveTime 线程存活时间(在corePoreSize<*<maxPoolSize情况下才有用)
 * @param unit 存活时间的时间单位
 * @param workQueue 等待执行的任务队列
 * @param handler 阻塞处理的策略
 */
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

ThreadPoolExecutor线程池通过corePoolSize与workQueue来进行交互。如果需要执行的线程数少于corePoolSize,它不会进入workQueue队列,而是直接新建对应数量的线程来执行。如果需要执行的线程数多于corePoolSize,它会把暂时未能处理的线程放入workQueue等待队列,执行器在执行线程任务时会优先访问workQueue队列。当需要执行的线程无法进入workQueue队列,且它们的数量多于maximumPoolSize时,线程的创建会被拒绝。下面引入了三种通用的排队策略:

  • 直接传递(Direct handoffs)。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  • 无界队列(Unbounded queues)。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  • 有界队列(Bounded queues)。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
注:当线程池的饱和策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
1、ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2、ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
3、ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4、ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。

ThreadPoolExecutor线程池有五种状态:

  • 运行中(RUNNING):该状态的线程池会接收新任务,并处理阻塞队列中的任务;
  • 关闭状态(SHUTDOWN):该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;
  • 暂停状态(STOP):该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;
  • 整理状态(TIDYING):该状态表示线程池对线程进行整理优化,任务都已终止,workerCount为零,过渡到状态清理的线程将运行terminate()钩子方法;
  • 终止状态(TERMINATED):terninate()已执行完成。
状态间转换可调用的函数:
RUNNING -> SHUTDOWN :可以调用shutdown()或finalize();
(RUNNING or SHUTDOWN) -> STOP: 可以调用shutdownNow();
TIDYING -> TERMINATED:可以调用terminated()或awaitTermination()