线程池带来的好处:
- 降低资源消耗
通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 - 提高响应速度
当任务到达时,任务可以不需要等到线程创建就能立即执行。 - 提高线程的可管理性
线程是稀缺资源,使用线程池可以统一分配、调优和监控。
实现原理
当提交一个新任务到线程池时,线程池的主要处理流程
- 判断核心线程池里的线程是否都在执行任务。
如果不是,则创建一个新的工作线程来执行任务;
如果核心线程池的线程都在执行任务,则进入下个流程。 - 判断工作队列是否已满。
如果没有,则将新提交的任务存储在这个工作队列;
如果已满,则进入下个流程。 - 判断线程池的线程是否都处于工作状态。
如果不是,则创建一个新的工作线程来执行任务;
如果是的,则交给饱和策略来处理这个任务。
ThreadPoolExecutor
执行execute()
方法的示意图
ThreadPoolExecutor执行execute()
分4种情况:
- 如果当前运行的线程少于
corePoolSize
,则创建新的线程来执行任务。(该步骤需要获取全局锁) - 如果运行的线程等于或多于
corePoolSize
,则将任务加入到BlockingQueue
。 - 如果无法将任务加入到
BlockingQueue
(队列已满),则创建新的线程来执行任务。(该步骤需要获取全局锁) - 如果创建新线程将使当前运行的线程超出
maximumPoolSize
,任务将被拒绝,并调用RejectedExecutionHandler
的rejectedExecution()
方法。
线程池的使用
创建
|
|
不过在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:
corePoolSize(核心线程池的大小)
corePoolSize,线程池的基本大小。当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于corePoolSize就不会再创建。
调用prestartAllCoreThreads()
线程池会提前创建并启动所有基本线程。
workQueue(任务队列)
workQueue任务队列:用于保存等待执行的阻塞队列。
有以下几种选择:
- ArrayBlockingQueue
ArrayBlockingQueue
是数组实现的线程安全的有界的阻塞队列,FIFO。 - LinkedBlockingQueue
LinkedBlockingQueue
是单向链表实现的阻塞队列,FIFO。 - SynchronousQueue
SynchronousQueue
是一个不存元素的阻塞队列。每一个插入操作必须等待另一个线程调用移除操作,否则一直处于阻塞状态。吞吐量高于LinkedBlockingQueue
和ArrayBlockingQueue
。 - PriorityBlockingQueue
PriorityBlockingQueue
是一个具有优先级的无界阻塞队列。
maximumPoolSize(线程池最大数量)
maximumPoolSize是线程池允许创建的最大线程数。如果任务队列满了,并且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程执行任务。
如果使用了无界阻塞队列,则该参数就没有效果。
ThreadFactory
ThreadFactory用于设置创建线程的工厂,可以通过线程工厂来给每个创建出来的线程设置更有意义的名字。
Guava框架提供的
ThreadFactoryBuilder
可以快速给线程池里的线程设置有意义的名字。
RejectedExecutionHandler(饱和策略)
RejectedExecutionHandler饱和策略:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认情况下是AbortPolicy
。
Java线程池框架提供了4种策略:
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardPolicy:不处理,直接丢弃掉。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
keepAliveTime(线程活动保持时间)
线程池的工作线程空闲后,保持存活的时间。
任务多且任务执行时间较短,可以将该参数设置大点,提高线程的存活率。
TimeUnit(线程活动保持时间的单位)
可选单位有:DAYS、HOURS、MINUTES、MILLISECONDS、MICROSECONDS、NANOSECONDS。
提交任务
可以使用两个方法向线程池提交任务:execute()
和submit()
。
execute()
execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
submit()
submit()
方法用于提交需要返回值的任务。线程池会返回一个Future
类型的对象,通过该对象可以判断任务是否执行成功,并通过Future的get()
方法获取返回值,get()
方法会阻塞当前线程直到任务完成,get(long timeout, TimeUnit unit)
方法会阻塞当前线程一段时间后立即返回,这时任务肯能没有执行完。
submit()方法实际上将任务构造成了
FutureTask
然后调用execute()方法执行。
关闭线程池
可以调用线程池的shutdown()
或者shutdownNow()
方法来关闭线程池。
原理:遍历线程池中的工作线程,然后逐个调用线程的interrupt()
来中断线程,所以无法响应中断的任务可能无法终止。