Java线程池主要用于管理线程组及其运行状态,以便Java虚拟机更好地利用CPU资源。Java线程池的工作原理为:JVM先根据用户的参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果线程数量超过了最大线程数量(用户设置的线程池大小),则超出数量的线程排队等候,在有任务执行完毕后,线程池调度器会发现有可用的线程,进而再次从队列中取出任务并执行。
线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数,以保证系统高效(通过线程资源复用实现)且安全(通过控制最大线程并发数实现)地运行。
线程复用
在Java中,每个Thread类都有一个start
方法。在程序调用start方法启动线程时,Java虚拟机会调用该类的run方法。前面说过,在Thread类的run方法中其实调用了Runnable对象的run方法,因此可以继承Thread类,在start方法中不断循环调用传递进来的Runnable对象,程序就会不断执行run方法中的代码。可以将在循环方法中不断获取的Runnable对象存放在Queue中,当前线程在获取下一个Runnable对象之前可以是阻塞的,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行。这样就简单实现了一个线程池,达到了线程复用的效果。
线程池的核心组件和核心类
Java线程池主要由以下4个核心组件组成。
- 线程池管理器:用于创建并管理线程池。
- 工作线程:线程池中执行具体任务的线程。
- 任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口,线程中的任务才能够被线程池调度。
- 任务队列:存放待处理的任务,新的任务将会不断被加入队列中,执行完成的任务将被从队列中移除。
Java中的线程池是通过Executor框架实现的,在该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTask这几个核心类,具体的继承关系如图所示:
其中,ThreadPoolExecutor是构建线程的核心方法,该方法的定义如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
ThreadPoolExecutor构造函数的具体参数如表所示:
Java线程池的工作流程
Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。
- 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务。
- 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中。
- 在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。
- 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。
- 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
- 在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。因此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。
具体的流程如图所示:
线程池的拒绝策略
若线程池中的核心线程数被用完且阻塞队列已排满,则此时线程池的线程资源已耗尽,线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全,线程池将通过拒绝策略处理新添加的线程任务。JDK内置的拒绝策略有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy这4种,默认的拒绝策略在ThreadPoolExecutor中作为内部类提供。在默认的拒绝策略不能满足应用的需求时,可以自定义拒绝策略。
1.AbortPolicy
AbortPolicy直接抛出异常,阻止线程正常运行,具体的JDK源码如下:
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
//直接抛出异常信息,不做任何处理
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " +e.toString());
}
}
2.CallerRunsPolicy
CallerRunsPolicy
的拒绝策略为:如果被丢弃的线程任务未关闭,则执行该线程任务。注意,CallerRunsPolicy拒绝策略不会真的丢弃任务。具体的JDK实现源码如下:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (! e.isShutdown()) {
r.run();//执行被丢弃的任务r
}
}
3.DiscardOldestPolicy
DiscardOldestPolicy
的拒绝策略为:移除线程队列中最早的一个线程任务,并尝试提交当前任务。具体的JDK实现源码如下:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if(! e.isShutdown()){
e.getQueue().poll(); //丢弃(移除)线程队列中最老(最后)的一个线程任务
e.execute(r); //尝试提交当前任务
}
}
4.DiscardPolicy
DiscardPolicy
的拒绝策略为:丢弃当前的线程任务而不做任何处理。如果系统允许在资源不足的情况下丢弃部分任务,则这将是保障系统安全、稳定的一种很好的方案。具体的JDK实现源码如下:
//直接丢弃线程,不做任何处理
public void rejectedExecution(Runnable r, ThreadPoolExecutor e){
}
5.自定义拒绝策略
以上4种拒绝策略均实现了RejectedExecutionHandler
接口,若无法满足实际需要,则用户可以自己扩展RejectedExecutionHandler
接口来实现拒绝策略,并捕获异常来实现自定义拒绝策略。下面实现一个自定义拒绝策略DiscardOldestNPolicy,该策略根据传入的参数丢弃最老的N个线程,以便在出现异常时释放更多的资源,保障后续线程任务整体、稳定地运行。具体的JDK实现源码如下:
public class DiscardOldestNPolicy implements RejectedExecutionHandler{
private int discardNumber = 5;
private List<Runnable> discardList =new ArrayList<Runnable>();
public DiscardOldestNPolicy (int discardNumber) {
this.discardNumber = discardNumber;
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if(e.getQueue().size() > discardNumber){
// 批量移除线程队列中的discardNumber个线程任务
e.getQueue().drainTo(discardList, discardNumber);
discardList.clear(); // 清空discardList列表
if (! e.isShutdown()) {
e.execute(r); // 尝试提交当前任务
}
}
}
}
评论前必须登录!
注册