Java线程池工作原理

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这几个核心类,具体的继承关系如图所示:
Java线程池工作原理

其中,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线程池的工作流程

Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。

  • 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务。
  • 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中。
  • 在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。
  • 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。
  • 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
  • 在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。因此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。

具体的流程如图所示:
Java线程池工作原理

线程池的拒绝策略

若线程池中的核心线程数被用完且阻塞队列已排满,则此时线程池的线程资源已耗尽,线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全,线程池将通过拒绝策略处理新添加的线程任务。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); // 尝试提交当前任务  
            } 
        } 
    }
}
赞(1)

评论 抢沙发

评论前必须登录!