Java 并发之线程池 ThreadPoolExecutor

简介

Executor
public interface Executor {
		void execute(Runnable command);  
}

An object that executes submitted Runnable tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads. For example, rather than invoking new Thread(new(RunnableTask())).start() for each of a set of tasks, you might use:
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());

顶层的接口,用来执行提交的 Runnable 任务。这个接口提供了一种将任务提交和任务运行机制(包括线程的使用、调度等)解耦的方式。总而言之,它是一个顶层接口。

ExecutorService
public interface ExecutorService extends Executor {

    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    ...
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
    ...
    <T> T invokeAny(Collection<? extends Callable<T>> tasks throws InterruptedException, ExecutionException;
    ...
}

An Executor that provides methods to manage termination and methods that can produce a Future for tracking progress of one or more asynchronous tasks

同样是一个接口,继承于 Executor 接口,扩充了线程池关闭、返回 Future(用于获取执行结果)等方法。

ThreadPoolExecutor
public abstract class AbstractExecutorService implements ExecutorService {
    //......  
    // 提供了 ExecutorService 中相关抽象方法基本实现
}
public class ThreadPoolExecutor extends AbstractExecutorService {
    //...
}

这个就是线程池的具体实现类,面向我们开发者使用的。

Executors
public class Executors {
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    } 
}

全是一些静态方法,可以看做是对 ThreadPoolExecutor 创建的封装,是一个工具类或者说是创建线程池的工厂。

线程池的好处

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
  3. 提高线程的客观理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以同一进行分配、调优和监控。

Executors 的基本使用

newFixedThreadPool

创建一个固定线程数量(传入方法的参数)的线程池。

public class MyTest1 {

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(3);

        IntStream.range(0, 4).forEach(index -> executorService.submit(() -> IntStream.range(0, 3).forEach(i -> System.out.println(Thread.currentThread().getName()))));

        executorService.shutdown();
    }
}

在这里插入图片描述
我们创建了一个线程数固定为3的线程池,提交了4个任务,从输出可以看出,总共创建了3个线程。

newSingleThreadPool

只需要把上面 ExecutorService executorService = Executors.newFixedThreadPool(3); 改成 ExecutorService executorService = Executors.newSingleThreadExecutor();
在这里插入图片描述
newSingleThreadExecutor = newFixedThreadPool(1)

newCachedThreadPool

在这里插入图片描述
提交了4个任务,创建了4个线程。

这三个工厂方法,实际上最后都是在调用 ThreadPoolExecutor 类来创建线程池。

ThreadPoolExecutor

构造器参数
  • int corePoolSize:线程池核心线程数量,即使线程池处于任务空闲期间,那么这些线程也不会被回收掉(除非 allowCoreThreadTimeOut 设置为 true,这个可以在理解整体流程之后再考虑)
  • int maximumPoolSize:线程池最大线程数
  • long keepAliveTime,TimeUnit unit:这两个参数一起使用,指定线程处于空闲状态多久后被回收,直至当前线程数等于 corePoolSize。
  • BlockingQueue<Runnable> workQueue:工作队列或阻塞队列,当核心线程已经全部被占用时,向线程池所提交的任务会放置到阻塞队列当中,队列可以是有界的,也可以是无界的。
  • ThreadFactory threadFactory:线程工厂,用于创建新的线程并被线程池管理。
  • RejectedExecutionHandler handler:当线程池中的线程都忙于执行任务且阻塞队列也已经满了的情况下,新到来的任务该如何被处理和对待。
整体执行策略

我们先编写一个示例代码来了解 ThreadPoolExecutor 的基本使用,进而分析其整体的执行策略。

class Task implements Runnable {
    private final String name;

    public Task(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void run() {
        System.out.println(name + " executed by " + Thread.currentThread().getName());
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println(name + " complete");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MyTest2 {

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), (r, e) -> {
            if (r instanceof Task) {
                System.out.println(((Task) r).getName() + " 被拒绝了!");
            }
        });

        
        IntStream.range(1, 16).forEach(i -> {
            executor.execute(new Task("Task " + i));
            System.out.println("after task " + i + " submitted, poolSize = " + executor.getPoolSize() + ", queue size = " + executor.getQueue().size());
            if (i == 1) {
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
      
        // ---------------------------------------------------------

        TimeUnit.SECONDS.sleep(15);

        executor.execute(new Task("Postponed Task 1"));
        executor.execute(new Task("Postponed Task 2"));
        executor.execute(new Task("Postponed Task 3"));

        System.out.println("after sleep 15 seconds. poolSize = " + executor.getPoolSize());

        TimeUnit.SECONDS.sleep(15);

        System.out.println("after sleep 30 seconds. poolSize = " + executor.getPoolSize());

        executor.execute(new Task("Postponed Task 4"));
        executor.execute(new Task("Postponed Task 5"));
        executor.execute(new Task("Postponed Task 6"));
      
        executor.shutdown();
    }
}

  1. Task 类很简单,接收一个任务名称,在 run 方法里输出当前任务名以及执行线程名,休眠3秒后输出任务结束。
  2. 创建了一个线程池,其核心线程数为3,最大线程数为6,非核心线程的超时时间为5秒,工作队列是一个容量为5的有界队列,拒绝策略就是 打印提交任务的名字然后该任务会被丢弃。
  3. 提交了16个任务到线程池,每次提交完任务打印线程池的 poolSize 以及队列的大小,并且在提交了第一个任务之后休眠了5秒。
  4. 后面的这一摞可以先不看,最后把线程池关闭。

接下来我们结合输出来一步步地理解线程池的执行策略。输出很长,我们分成几个部分。

  • 核心线程的工作原理
    在这里插入图片描述

Task 1 任务提交之后,主线程进行了5秒钟休眠,我们的任务是固定休眠3秒的,所以在提交 Task 2 之前,Task 1 已经执行完毕了(执行线程为 pool-1-thread-1),但是 Task 2 提交后,线程池还是创建了新的线程 pool-1-thread-2 来执行 Task 2,即使这个时候 pool-1-thread-1 已经处于空闲状态,Task 3 同 Task2,创建了新的线程。

所以,在线程池中线程数小于 corePoolSize 时,来一个任务创建一个新的线程来执行。

  • 工作队列
    在这里插入图片描述

Task 4 由于 pool-1-thread-1 空闲,直接交给它来执行。接下来的 Task 5 ~ Task 9,由于3个核心线程都处于工作状态,放入到了工作队列当中。

所以,工作队列的作用可以归结为当线程池中核心线程已满且都处于工作状态时,对提交的任务进行缓冲。

  • 工作队列已满,创建非核心线程
    在这里插入图片描述

​ 与我们当前要阐述的输出我给马赛克了。当提交 Task 10 之后,由于工作队列已经满了,创建了新的 pool-1-thread-4 线程用于执行 Task 10。queue size 这个时候一直等于5,poolSize 在变大,一直到 Task 12 提交。

  • 拒绝策略
    在这里插入图片描述

Task 13 ~ Task 15,这三个任务提交后 poolSize 和 queueSize 都没有变化,走到了我们的拒绝策略。所谓的拒绝策略,就是当线程池中工作队列已满且线程数达到 maximunSize,就会触发传入的 RejectedExecutionHandler 接口的回调。
在这里插入图片描述

  • 一个问题:核心线程指的是先创建的线程吗?

    我们的回答是:不是。回到我们上面的测试代码后面一段:
    在这里插入图片描述

在提交完16个 Task 之后,我们休眠了15秒,再次提交3个任务,再重复一次。我们的目的是什么呢?要看休眠等待了15秒和30秒之后,接下来线程池里剩下的线程都有谁,由于我们没有设置 allowCoreThreadTimeOut 标志,核心线程是会在线程池一直存活着的。
在这里插入图片描述
从输出可以看出,并不是一开始创建的 pool-1-thread-1~3 线程。

  • 两个接口:BlockingQueue & ThreadFactory

A Queue that additionally supports operations that wait for the queue to become non-empty when retrieving an element, and wait for space to become available in the queue when storing an element.

阻塞队列实际上就是支持生产者-消费者模式的队列。常用的有 ArrayBlockingQueue、LinkedBlockingQueue,一个是基于数组实现的,另一个是基于链表实现的,还有 okhttp 中线程池使用的阻塞队列: SynchronousQueue,它没有容量。对实现细节感兴趣的可以自行搜索下。
在这里插入图片描述

在我们的测试代码里,是没有指定这个参数的,那么 ThreadPoolExecutor 中默认的实现是啥呢?
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从这里的实现我们就能够知道为啥线程池里的线程名默认是 pool-x-thread-x 的形式了,而且默认实现当中,创建的线程都是非守护线程,线程优先级也是普通级别的。

所以线程池整体的工作策略我们可以用流程图简单概括为:
在这里插入图片描述
用一句概括简单就是:线程池总是朝着核心线程数进行收敛。

下面的篇幅我们通过源码分析一下我们得到的结论。

状态、线程数量与位运算

在正式分析流程源码之前,我们需要关注一下线程池中关于状态和线程数量的表示形式。
在这里插入图片描述
Integer.SIZE 等于 32,一个 int 型变量又4个字节存储,所以这里是通过高三位表示线程池的状态,其余低29位表示线程的数量。runStateOf 用来提取状态值,workerCountOf 用来提取数量,ctlOf 则是将传入的两个值(rs 表示状态,wc 表示数量)合并。线程池的状态有五种:

  • RUNNING:Accept new tasks and process queued tasks。线程池可以接受新的任务提交,并且还可以正常处理阻塞队列中的任务。
  • SHUTDOWN:Don’t accept new tasks, but process queued tasks。不再接受新的任务提交,不过线程池可以继续处理阻塞队列中的任务。
  • STOP:Don’t accept new tasks, don’t process queued tasks, and interrupt in-progress tasks。不再接受新的任务,同时还会丢弃阻塞队列中的既有任务;此外,它还会中断正在处理中的任务。
  • TIDYING:All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method。所有的任务都执行完毕后(同时也涵盖了阻塞队列中的任务),当前线程池中的活动的线程数量降为0,将会调用 terminated 方法。
  • TERMINATED:terminated() has completed。线程池的终止状态,当 terminated 方法执行完毕后,线程池将会处于该状态之下。

从具体状态对应的值的大小来看,从 RUNNING 到 TERMINATED,是依次变大的。

ThreadPoolExecutor 中的 terminated() 方法是个空实现,子类可以选择做具体的实现,是一个生命周期回调方法。

/**
 * Method invoked when the Executor has terminated.  Default
 * implementation does nothing. Note: To properly nest multiple
 * overridings, subclasses should generally invoke
 * {@code super.terminated} within this method.
 */
protected void terminated() { }
任务提交

我们从 ThreadPoolExecutor 的 execute 方法开始:
在这里插入图片描述

红框里的注释描述的内容和我们前面概括的基本工作原理是一致的。

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 1.
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2.
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
     }
     // 3.
     else if (!addWorker(command, false))
        reject(command);
}
  • 注释1:看当前线程的数量是否小于 corePoolSize,如果条件成立,调用 addWorker(command, true),添加成功后直接返回,添加失败后再次获取 ctl 的值。
  • 注释2:程序能够执行到这里,说明当前线程数量已达到核心线程数。然后判断当前线程池是否处于 RUNNING 状态,并且将提交的任务添加到工作队列。在条件满足下,又进行了状态的判断,如果不处于 RUNNING 状态,则将刚提交的任务从工作队列中移除,并且调用 reject() 方法;最后的 else if 里判断了线程数量,如果为0,则调用 addWorker(null, false)。在大多数情况下,可以理解为外层的 if 所做的事就已经足够了,在条件满足里的操作实际上是一种二次校验以及小概率事件。
  • 注释3:程序能够执行到这里,可能有两种情况:线程池已经不是 RUNNING 状态了;线程数量已经 >= corePoolSize 并且阻塞队列已满。尝试调用 addWorker,添加失败调用 reject()。

从代码层面,我们再一次巩固了线程池整体的工作策略。我们现在着重看下 reject() 和 addWorker() 方法的实现。

/**
 * Invokes the rejected execution handler for the given command.
 * Package-protected for use by ScheduledThreadPoolExecutor.
 */
final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

很简单,就是进行一个回调。

重中之重就是这个 addWorker()。对于 addWorker() 的调用,execute() 方法中出现了三种形式:

addWorker(command, true);
addWorker(null, false);
addWorker(command, false);

我们来看 addWorker 的两个参数:
在这里插入图片描述

  1. firstTask:新创建的线程会先执行的任务,如果没有的话传入 null,这个时候线程会直接尝试从阻塞队列中取出任务来执行。
  2. core:新创建的线程是否是核心线程,也即当前线程池中线程数量是否超过 corePoolSize。

addWorker() 的实现很长,我们分批来看。
在这里插入图片描述

这里使用到了 label 跳转,并且有两层 for 的死循环。

  • 红框1:我们把 if 里的条件判断换一种实现方式:

    if (rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty)) {
    		return false;
     }   
    

    换句话说,在以下三种情况下会导致添加 Worker 失败(返回 false):

    1. rs > SHUTDWON,即当前线程池处于 STOP、TIDYING、TERMINATED 三种状态时,直接添加失败;
    2. rs >= SHUTDOWN && firstTask != null,当线程池处于非 RUNNING 状态并且继续提交任务时,添加失败;
    3. rs >= SHUTDOWN && workQueue.isEmpty,当线程池处于非 RUNNING 状态并且阻塞队列为空时,添加失败。
  • 红框2:它同样在以下三种情况下会导致添加 Worker 失败

    1. wc >= CAPACITY,CAPACITY 是线程池线程计数的最大值,即当前线程池中线程数超过了低29位所能表示的最大值,添加失败;
    2. core && wc >= corePoolSize,即是创建核心线程情景下线程数已经大于等于 corePoolSize,添加失败;
    3. !core && wc >= maximumPoolSize,即是在创建非核心线程模式情景下线程数已经大于等于 maximumPoolSize,添加失败。
  • 红框3:

    private boolean compareAndIncrementWorkerCount(int expect) {
        return ctl.compareAndSet(expect, expect + 1);
    }
    

​ 尝试使用 CAS 将 ctl 的值加1,如果修改成功,跳出两层循环,向下执行。

  • 红框4:再次获取线程池运行状态和外层循环最开始读取的线程池运行状态进行对比,如果在这期间线程池的状态发生了变化而导致判断不相等,则跳出内层死循环,重新进入外层死循环重复以上判断。

可见这一段代码就是在进行线程数量和线程池状态的校验

我们接着往下看 addWorker 的逻辑:
在这里插入图片描述

一开始就定义了两个局部变量,见名知意,workerStarted 表示线程是否启动成功,workerAdded 表示线程(封装在 Worker 对象中)是否加入到 workers 中进行管理。

  • 红框1:创建了一个 Worker 对象,这个 Worker 对象是个啥呢?

在这里插入图片描述
​ 继承自 AQS(笔者有一篇关于 AQS 的文章),并且实现了 Runnable 接口,封装了线程对象、初始的任务 Task 以及一个 completedTasks(表示完成了多少 Task)。再看构造器,先看第二行和第三行,记录初始 Task 以及调用上面介绍过的线程工厂创建线程对象。最后来看构造器的第一行:设置 AQS 中的 state 值为 -1。这是要干啥呢?
在这里插入图片描述

理解了 AQS 的原理,这里就很好理解,因为0表示无锁状态,1表示上锁状态,调用线程的 interrupt() 方法之前会判断 state 的值是否大于等于0,也就是说,如果 state 的值等于 -1,interrupt() 方法是没有办法得到执行的,这样就实现了避免被中断。

关于 Worker 类,我们还需要记住一点,重写了 Runnable 的 run() 方法,并且在实现中调用了 runWorker() 方法,后面要用到。

  • 红框2:一个 ReentrantLock 锁,将下面的操作锁住,保护起来,避免出现并发问题。

  • 红框3: if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)),也就是说在两种情况下条件判断为 true:

    1. 线程池处于 RUNNING 状态;
    2. 线程池处于 SHUTDOWN 状态,没有新的任务提交。

    只有在这两种情况下才会考虑添加新的 Worker。这一点也和前面关于线程池状态的描述吻合。

  • 红框4:
    在这里插入图片描述
    ​ 为了判断传入 Worker 对象中的 Thread 对象是否已经调用了 start() 方法,已经调用就会抛出异常。

  • 红框5:

    将新创建的 Worker 对象放入 workers(是个 HashSet) 中,并且更新 largestPoolSize 的值以及记录已添加。

  • 红框6:在添加成功的情况下,调用线程的 start() 方法,记录启动成功。因为我们在 Worker 构造器里将当前 Worker 对象传入了 Thread 对象当中,会触发 run() 方法的调用,先放一下,后面再进行分析这里的逻辑。

  • 红框7:finally 块中,只要是线程没有成功启动的情况下,会调用 addWorkerFailed() 进行一个回滚。

    在这里插入图片描述
    前面两行很好理解,从集合当中移除以及将线程计数减1,最后一行会调用 tryTerminate(),这个方法里有一些逻辑,我们放入到线程池关闭逻辑再去看。

  • 红框8:只有成功将线程启动了,返回值才会为 true,表示 addWorker 成功。

所以目前我们分析线程池的数量和状态的校验逻辑、线程管理启动逻辑,具体线程池中的线程是如何进行任务处理的,我们目前还一无所知,接下来的重心就是 Worker 类中的 run() 方法。

/** Delegates main run loop to outer runWorker  */
public void run() {
    runWorker(this);
}

在这里插入图片描述

  • 红框1:取出封装在 Worker 中 firstTask(我们在 execute() 方法里传入的,最后传入 addWorker() 里),在后面的循环里会去调用 Runnable 的 run() 方法,最后置 null。

  • 红框2:还记得 Worker 构造器里的第一行代码不是把 AQS 的 state 值置为 -1 了吗,这里调用 unlock() 将值改为0,这样,线程就可以中断了。

  • 红框3:while 里,如果 firstTask 本身不为 null,直接进入循环体,不会调用后面的 getTask(),反之则会从 getTask() 中取出 task,不为 null 再进入循环体。

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
    
        // 不断从工作队列中取出任务直到拿到 Task 或者 null。
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
    
            /*
             1. rs >= SHUTDOWN && rs >= STOP: 等价于 rs >= STOP,所以线程池处于 STOP 及以后的状态时,if 条件判断为 true。
             2. rs >= SHUTDOWn && workQueue.isEmpty():线程池处于 SHUTDOWN 及以后的状态,并且工作队列为空,if 条件判断为 true。
             满足以上两种情况下,会直接将线程数减1,返回 null,返回 null 也就意味着不会进入 while 循环体,当前线程的执行也就完毕了。
            */
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
    
            int wc = workerCountOf(c);
    
            // Are workers subject to culling?
            // 如果我们设置了 allowCoreThreadTimeOut (true 表示核心线程空闲超时后也会退出) 或者当前线程数已经超过了核心线程数。
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
    
            /*
            第一次看这里可以跳过,直接看下面的 try 逻辑。
            如果是回头看这里的逻辑:
            (线程数已经超过了最大线程数或者当前需要使用超时机制且从上一次从阻塞队列当中取任务时已经超时时) && (线程数大于1 或者工作队列为空),满足这些条件后返回 null,不会进入 runWorker() 中 while 循环体,当前线程的执行也就完毕。
            */
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
    
            try {
                /*
                allowCoreThreadTimeOut == true || wc > corePoolSize,则使用允许超时机制的 poll 方法从阻塞队列中取出任务,否则使用只要取不出任务就会一直阻塞的 take() 方法,使得当前线程一直阻塞在这个地方。所以我们在构造器里设置的线程空闲超时回收机制,实际上是通过阻塞队列中的 poll 机制进行实现的。
                */
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
    

在这里插入图片描述

​ 关于 wc > maximumPoolSize 这个地方,我最初理解这个地方的时候,感觉这个地方应该不会成立才对,为啥要判断呢?是因为 setMaximumPoolSize() 可以动态设置,所以这个地方的判断是有必要的。

  • 红框4:判断线程池的状态是否需要中断线程。

  • 红框5、7:子类可以实现在任务开始时做一些额外的工作。
    在这里插入图片描述

  • 红框6:调用 run() 方法,开始执行具体的 Task。

  • 红框8:task 置为 null 很关键,只有置为 null 才会从工作队列中取出任务,否则会导致 task 重复执行。完成的任务计数加1,最后解锁。

关于 runWorker() 方法,最后还有一个 finally 块:

finally {
    processWorkerExit(w, completedAbruptly);
}

completedAbruptly == true,则说明是异常退出,反之是无法取出 Task 而使 completedAbruptly == false 的正常退出。
在这里插入图片描述

  • 红框1:异常退出时才将线程数减1,正常退出时线程数减1的操作已经在 getTask() 里已经处理了。
  • 红框2:将当前线程完成的任务数加到线程池完成任务总数上,并将 Worker 移除。
  • 红框3、红框4:在线程池处于 RUNNING 或者 SHUTDOWN 状态时,进入3和4的逻辑。3则是在正常退出的情况下进行线程数的校验,如果当前线程数大于等于 min,意味着当前是需要新增线程来执行的,直接 return。反之,如果线程数小于 min 或者是异常退出,需要重新调用 addWorker。

这样,线程池整个提交流程就已经分析完了,有了这段分析的基础,我们再回过头去分析前面的测试代码的每一行输出,每一行都值得思考,这样对线程池的理解就会更加深入。比如线程空闲,本质上到底指的是什么?核心线程是如何体现的?这些问题留给大家。

线程池关闭

以 shutdown() 方法为例:
在这里插入图片描述

  • “1”:修改线程池状态为SHUTDOWN

    在这里插入图片描述

  • “2”:

    /**
     * Common form of interruptIdleWorkers, to avoid having to
     * remember what the boolean argument means.
     */
    private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
    

在这里插入图片描述
​ 就是中断空闲的线程。那么怎么知道是空闲线程呢?这里巧妙地使用了 tryLock(),如果当前有任务正在执行,在 runWorker 方法里的 while 循环是会上锁进行操作的,那么 tryLock() 就会返回 false,就不会被中断,反之返回 true,说明阻塞在 getTask() 里,这时线程可以被中断。

  • “3”:生命周期回调,子类可以提供实现。

    /**
     * Performs any further cleanup following run state transition on
     * invocation of shutdown.  A no-op here, but used by
     * ScheduledThreadPoolExecutor to cancel delayed tasks.
     */
    void onShutdown() {
    }
    
  • “4”:

    在这里插入图片描述

  1. 判断线程池的状态,正在运行或者已经清理完毕或者 SHUTDOWN 状态下工作队列不为空,这三种情况下直接 return。

  2. 中断线程,修改状态和将线程数清零。

  3. termination 是整个线程池 mainLock 的 Condition 对象,调用 signalAll(),我们看下 await() 是哪里调用的。

    在这里插入图片描述

参考资料:

《Java 并发编程的艺术》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值