1.线程池介绍
线程池是一种线程使用模式。线程由于具有空闲(eg:等待返回值)和繁忙这种不同状态,当数量过多时其创建、销毁、调度等都会带来开销。线程池维护了多个线程,当分配可并发执行的任务时,它负责调度线程执行工作,执行完毕后线程不关闭而是返回线程池,可以执行后续其他任务。举例来说,外卖餐厅对每个订单分配一个临时工,完成订单立刻辞退,成本和管理开销巨大,而且如果取餐点的空间有限,大量的人挤满在那反而会影响工作效率,因此选择签下固定数量的外卖小哥让他们不断往返于目的地和店家之间配送。
通俗来讲,线程池就是为了减少开销而复用线程,实现了任务内容和线程分离的一种机制。如果机器有100核而且其他资源充足,直接启动100个无冲突的并发线程应该是比使用线程池要快,但是受限于目前的硬件能力,线程池就很有必要了。Java的Executor框架用于执行异步任务,方法 executor(Runnable runnable) 可以接收Runnable对象。Executor有一个子类接口ExecutorService,提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法。
2.工厂模式创建线程池
Excutors运用工厂模式提供线程池实现方式,返回ExecutorService
newSingleThreadExecutor,单线程的线程池,实际上是串行执行任务,如果该线程出现异常则会有新线程替代它工作,其优势是任务会按照提交顺序执行。
newFixedThreadPool,固定大小线程池,每次新提交任务时如果线程数没达到最大就创建新线程执行任务,否则任务进入等待状态。
newCachedThreadPool,可缓存的线程池,大小依赖于操作系统(JVM)最大值。线程池大于任务数时回收部分空闲(60s)线程,任务数增加时添加新线程。
问题在于,前两者请求处理队列的堆积会消耗内存,第三个创建过多线程也会这样。同时,拒绝策略在Executors中无效。
3.自定义创建
ThreadPoolExecutor是线程池的实现方法签名:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) //后两个参数为可选参数
- corePoolSize,核心线程数,同时处于执行状态的线程最大值(或者说,接单配送中的小哥人数上限)。当线程数少于该值时,新的任务会创建新的线程执行。allowCoreThreadTimeOut属性不设置为true(默认false)时,闲置的核心线程也不会销毁。
- maximumPoolSize,线程总数,等于核心线程+非核心线程数,不小于核心线程数。
- keepAliveTime,线程进入不活跃状态后到销毁前的时间,通过Unit参数设置单位。通常只适用于非核心线程,allowCoreThreadTimeOut为true时核心线程也适用。
- workQueue,任务队列,当核心线程满时新任务进入该队列等待,当队列满时创建非核心线程执行任务。
- SynchronousQueue:等于没有队列,任何任务都直接给线程处理,为防止线程数量达到最大值,通常设置maximumPoolSize为Integer.MAX_VALUE
- LinkedBlockingQueue:无限长阻塞队列,总线程数永远等于核心线程数,未执行的任务都在队列里排队
- ArrayBlockingQueue:定长队列,队列满时新建非核心线程,达到maximumPoolSize后出错
- DelayQueue,当任务实现Delayed接口时,任务入队等待到指定延时后开始执行
- threadFactory,线程工厂
- handler,任务被拒绝时的策略
- AbortPolicy,默认策略,将抛出RejectExecutorException
- CallerRunsPolicy,线程池未关闭时调用线程(任务提交者)直接执行该任务
- DiscardPolicy,直接丢弃不抛出异常
- DiscardOldestPolicy,丢弃队列最前的任务后重试当前任务
4.生命周期
ExecutorService的生命周期包括了运行、关闭和终止三种状态,在初始化创建时处于运行状态。使用submit方法执行Runnable或者Callable对象。shutdown方法等待提交的任务执行完成并不再接受新任务,在完成全部提交的任务后关闭;shutdownNow方法将强制终止所有运行中的任务并不再允许提交新任务。