一、线程池简介
在Java中,使用线程来异步执行任务。Java线程的创建与销毁需要一定的开销,如果我们为每个任务创建一个新的线程来执行,这些线程的创建与销毁将消耗大量的计算资源。Java的线程在此情况下,既是工作单元,也是执行机制。为了减少创建线程的额外开支,将工作单元与执行机制分离不失为一个好办法。由此,Java线程池自然的被设计出了。
合理地使用线程池带来极大的好处:
1)降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁带来的开销。
2)提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立刻执行。
3)提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗资源,而且会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
但是,要充分发挥线程池的优点,就必须掌握其使用方法和适用条件,对其实现原理也必须了如指掌。下面我们主要介绍线程池的主要实现类ThreadPoolExecutor。
二、线程池的状态
ThreadPoolExecutor中一共有5种状态。分别是:
- RUNNING:接受新的任务并且处理阻塞队列中的任务。
- SHUTDOWN:拒绝新的任务,但是会将阻塞队列中的任务完成。
- STOP:拒绝新的任务,抛弃阻塞队列中的任务,中断正在处理的任务。
- TIDYING:所有任务都已经执行完成,线程池中活动线程为0,阻塞队列也为空,此时将要调用 terminated()方法。
- TERMINATED:执行完terminated()方法,线程池处于终止状态。
三、线程池状态转换
- RUNNING=》SHUTDOWN
显示调用shutdown()方法,或者隐式调用finalize(),它里面调用了shutdown()方法。
- RUNNING or SHUTDOWN =》STOP
显式调用shutdownNow()方法。
- SHUTDOWN =》 TIDYING
当线程池和阻塞队列都为空时。
- STOP=》TIDYING
当线程池为空是。
- TIDYING=》TERMINATED
当terminated()的hook方法执行完成时。
四、线程池参数
- corePoolSize:线程池核心线程个数
- workQueue:阻塞队列,用于保存等待执行的任务。
- maximumPoolSize:线程池最大线程数量
- threadFactory:创建线程的工厂
- RejectedExecutionHandler:饱和策略,当队列满了并且活动线程数达到maximumPoolSize时采取的策略。
- keepAliveTime:存活时间,当活动线程数大于核心线程数并且处于闲置状态,这些闲置线程能存活的最大时间。
- TimeUnit:存活时间单位。
五、线程池主要处理流程
当向线程池提交一个任务之后,线程池的主要处理流程如图:
- 判断核心线程池里面线程是否已满。如果未满,则创建新的工作线程执行任务。如果已满,则进入下个流程。
- 判断工作队列是否满了。如果未满,则加入工作队列。如果满了,进入下个流程。
- 判断线程池线程数有没有达到最大线程数。如果满了,采取饱和策略。如果未满,创建线程执行任务。
之所以采取上述的设计思路,是因为1、3创建新线程需要获取全局锁,开销很大。在ThreadPoolExecutor预热之后,绝大部分execute()方法调用都是执行第二步,而这时不需要全局锁的。
六、RejectedExecutionHandler策略
当队列和线程池都满了,必须采取一种策略处理提交的新任务。默认是AbortPolicy。java 1.5中提供了四种策略:
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者线程来运行。
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,直接丢弃。