Java:ThreadPoolExecutor解析

目录

 

功能介绍

线程池相关类图

源码解析

基本概念

字段域

常量

线程构造重要字段

线程控制重要字段

方法

执行任务

ThreadPoolExecutor的关闭


功能介绍

线程池,顾名思义一个线程的池子,池子里存放了很多可以复用的线程,如果不用线程池类似的容器,每当我们需要创建新的线程时都需要去new Thread(),用完之后就被回收了,线程的启动回收都需要用户态到内核态的交互,频繁的创建开销比较大。而且随着线程数的增加,会引起CPU频繁的上下文切换严重影响性能。这时候线程池类似的容器就发挥出了作用。线程池里面的线程不但可以复用,而且还可以控制线程并发的数量,是CPU的性能达到最优。

线程池相关类图

源码解析

基本概念

工作线程:即用于执行任务的线程。

任务:将要执行的任务

缓存队列:工作线程全部被占用时,缓存任务的队列。

字段域

常量

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
//0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//1110 0000 0000 0000 0000 0000 0000 0000
//能接受新任务,队列中的任务可继续运行
private static final int RUNNING    = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,队列中的任务仍可继续执行
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,不再执行队列中的任务,中断所有执行中的任务(发中断消息)
private static final int STOP       =  1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
//所有任务均已终止,workerCount的值为0,转到TIDYING状态的线程即将要执行terminated()钩子方法.
private static final int TIDYING    =  2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
//terminated()方法执行结束
private static final int TERMINATED =  3 << COUNT_BITS;

 线程池的状态控制由AtomicInteger类型变量ctl 控制,其值为32位整形,其中前3位用于线程池状态,后29位用于线程数量控制。

线程池具有5个状态:

RUNNING    = 111
SHUTDOWN   = 000
STOP       = 001
TIDYING    = 010
TERMINATED = 011

 各状态之间可能的转变有以下几种:

RUNNING -> SHUTDOWN
    调用了shutdown方法,线程池实现了finalize方法。
    在finalize内调用了shutdown方法。
    因此shutdown可能是在finalize中被隐式调用的
(RUNNING or SHUTDOWN) -> STOP
    调用了shutdownNow方法
SHUTDOWN -> TIDYING
    当队列和线程池均为空的时候
STOP -> TIDYING
    当线程池为空的时候
TIDYING -> TERMINATED
    terminated()钩子方法调用完毕

 状态的解析

//初始化线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//获取当前线程的运行状态
//~CAPACITY = 1110 0000 0000 0000 0000 0000 0000 0000
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//获取当前的线程数
//CAPACITY = 0001 1111 1111 1111 1111 1111 1111 1111
private static int workerCountOf(int c)  { return c & CAPACITY; }
//或操作
// 111x xxxx xxxx xxxx xxxx xxxx xxxx xxxx | 0000 0000 0000 0000 0000 0000 0000 0100
// = 1110 0000 0000 0000 0000 0000 0000 0100
private static int ctlOf(int rs, int wc) { return rs | wc; }


    private static boolean runStateLessThan(int c, int s) {
        return c < s;
    }

    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }

    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

 状态的设计理念

  • 将运行状态设置为小于0的数,便于判断当前线程池是否处于running状态;
  • 仅使用32位存储线程池状态与线程池内的线程数,若需要获取线程池信息,只需要一个int即可
  • 采用位运算,提高了执行效率;
  • 同时,线程池状态还有扩增空间(23=8,目前只有5种状态),而线程池最大容量2^29,也可保证在绝大部分应用中是不会溢出的。而在源码中也声明了:如果在未来这个也成为一个问题,那么可以扩增为AtomicLong。

线程构造重要字段

  • corePoolSize:核心池的大小(即任务线程的个数),这个参数与后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;缓存队列即可以是有界的,也可以是无界的,仅当缓存队列不可加入任务,并且任务线程数不大于maximumPoolSize时,才又创建任务线程
  • maximumPoolSize:线程池最大线程数,它表示在线程池中最多能创建多少个线程;
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize:即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize;但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • unit:参数keepAliveTime的时间单位

线程控制重要字段

//待执行线程队列
private final BlockingQueue<Runnable> workQueue;
//锁,基于重入锁,线程池核心之一
private final ReentrantLock mainLock = new ReentrantLock();
//线程队列,这是该线程池内已有线程
//注意与workQueue的区别
private final HashSet<Worker> workers = new HashSet<Worker>();
//多线程协调通信
private final Condition termination = mainLock.newCondition();
//拒绝handler,用于线程池不接受新加线程时的处理方式
//分为系统拒绝(线程池要关闭等),与线程池饱和(已达线程池最大容量)
private volatile RejectedExecutionHandler handler;
//线程工厂,新建线程池时带入
private volatile ThreadFactory threadFactory;
//默认拒绝向线程池中新加线程的方式:丢弃
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();

方法

执行任务

使用ThreadPoolExecutor执行任务的时候,可以使用execute或submit方法,submit方法如下:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Runnable task, T result) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task, result);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

submit方法内部使用了execute方法,而且submit方法是有返回值的。在调用execute方法之前,使用FutureTask包装一个Runnable,这个FutureTask就是返回值。

execute()方法

源码如下:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {   
   // 如果工作线程的数量小于corePoolSize,表示可以创建线程直接用于运行任务。
        if (addWorker(command, true)) 
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) { 
   //工作线程的数量>=corePoolSize,则不能再创建工作线程了,需要把任务加入缓存队列中去。

        int recheck = ctl.get();
        //检查当前线程池状态是否不在Running状态了
        //若是,将线程cmd从等待队列内移除
        //这个时候存在一种case,线程池不处于running状态
        //但是remove失败了,这个时候看具体的queue处理了
        //线程池还是很忠实的去尝试interrupt

        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
   //如果缓存队列加入不了任务了,则又可以创建新的工作线程了。
    else if (!addWorker(command, false)) 
    //创建工作线程失败,则执行reject策略。
        reject(command);
}

提交一个新的任务的3种情况如下:

  • 如果当前正在执行的Worker数量比corePoolSize(基本大小)要小。直接创建一个新的Worker执行任务,会调用addWorker方法
  • 如果当前正在执行的Worker数量大于等于corePoolSize(基本大小)。将任务放到阻塞队列里,如果阻塞队列没满并且状态是RUNNING的话,直接丢到阻塞队列,否则执行第3步。丢到阻塞队列之后,还需要再做一次验证(丢到阻塞队列之后可能另外一个线程关闭了线程池或者刚刚加入到队列的线程死了)。如果这个时候线程池不在RUNNING状态,把刚刚丢入队列的任务remove掉,调用reject方法,否则查看Worker数量,如果Worker数量为0,起一个新的Worker去阻塞队列里拿任务执行
  • 丢到阻塞失败的话,会调用addWorker方法尝试起一个新的Worker去阻塞队列拿任务并执行任务,如果这个新的Worker创建失败,调用reject方法

注意

每次判断状态时,都必须重新获取状态。

addWorker()方法

addWorker关系着如何起一个线程,Worker是一个AQS的实现类(参考:AbstractQueuedSynchronizer源码解析),同时也是一个实现Runnable的类,使用独占锁,它的构造函数只接受一个Runnable参数,内部保存着这个Runnable属性,还有一个thread线程属性用于包装这个Runnable(这个thread属性使用ThreadFactory构造,在构造函数内完成thread线程的构造),另外还有一个completedTasks计数器表示这个Worker完成的任务数。Worker类复写了run方法,使用ThreadPoolExecutor的runWorker方法(在addWorker方法里调用),直接启动Worker的话,会调用ThreadPoolExecutor的runWork方法。需要特别注意的是这个Worker是实现了Runnable接口的,thread线程属性使用ThreadFactory构造Thread的时候,构造的Thread中使用的Runnable其实就是Worker。下面的Worker的源码:

private final class Worker    
    extends AbstractQueuedSynchronizer
    implements Runnable
{

    private static final long serialVersionUID = 6138294804551838833L;

    /** worker绑定的线程.null表示失败 */
    final Thread thread;
    /** 初始化时指定的任务,可为null. */
    Runnable firstTask;
    /** 完成任务数 */
    volatile long completedTasks;

    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        
        //把状态位设置成-1,这样任何线程都不能得到Worker的锁,除非调用了unlock方法。
        //这个unlock方法会在runWorker方法中一开始就调用,这是为了确保Worker构造出来之后,没有任何线程能够得到它的锁,
        //除非调用了runWorker之后,其他线程才能获得Worker的锁
        setState(-1); 
        this.firstTask = firstTask;
        // 使用ThreadFactory构造Thread,这个构造的Thread内部的Runnable就是本身,也就是Worker。
        //所以得到Worker的thread并start的时候,会执行Worker的run方法,也就是执行ThreadPoolExecutor的runWorker方法
        this.thread = getThreadFactory().newThread(this);
    }

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

    //0表示unlock,1表示lock
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }

    protected boolean tryAcquire(int unused) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    protected boolean tryRelease(int unused) {
        setExclusiveOwnerThread(null);
        setState(0);
        return true;
    }

    public void lock()        { acquire(1); }
    public boolean tryLock()  { return tryAcquire(1); }
    public void unlock()      { release(1); }
    public boolean isLocked() { return isHeldExclusively(); }

    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

addWork代码

//尝试向线程池内新增一个线程
private boolean addWorker(Runnable firstTask, boolean core) {
    //用于跳出外层循环的标签
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //若线程池处于非运行状态
        //且
        //或rs不处于SHUTDOWN状态(STOP、TIDYING、TERMINATED 之一)
        //或firstTask不为空   (非运行状态,不可以再增加Task了,所以firstTask不为空要返回false)
        //或缓冲队列为空   (size== 0,表示不可以插入元素了)
        //那么返回false,表明新增一个线程失败(执行firstTask 也失败)
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
            
        //此时线程池处于running状态,firstTask不为空
        //且缓冲队列不为空,此时需要新增一个线程
        for (;;) {
            //获取线程池当前线程数量
            int wc = workerCountOf(c);
            //若线程池超过最大容量,或大于设定的容量
            //corePoolSize与maximumPoolSize均为传入的参数
            //那么直接返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //线程池未过限,那么采用cas机制,将线程池计数器扩增1,跳出标签
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //获取当前线程池信息
            c = ctl.get();  // Re-read ctl
            //若线程池状态有变更,从标签处重新循环
            if (runStateOf(c) != rs)
                continue retry;
            //若线程池状态未变化,继续内层的for循环
        }
    }

    //上面若将线程池计数器加1了
    //这里就要对线程池扩增了(即增加一个工作线程)
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //创建一个线程实例
        w = new Worker(firstTask);
        //获取线程
        //不在创建实例时直接run该线程,是避免构造函数未执行完,就run导致的异常
        final Thread t = w.thread;
        if (t != null) {
            //重入锁
            final ReentrantLock mainLock = this.mainLock;
            //锁上,走起
            mainLock.lock();
            try {
                //获取线程池状态
                int rs = runStateOf(ctl.get());
                //若线程池状态为running状态
                //或为SHUTDOWN且传入的线程为空
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    //若新起的线程状态为活跃状态
                    if (t.isAlive()) // precheck that t is startable
                        //抛异常
                        throw new IllegalThreadStateException();
                    //否则向运行线程池内加上该线程
                    workers.add(w);
                    int s = workers.size();
                    //判断当前线程池是否为线程池最大
                    //是的话交换,做统计用
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    //新增标志位置为成功
                    workerAdded = true;
                }
            } finally {
                //重入锁解锁
                mainLock.unlock();
            }
            //判断线程是否加入成功
            if (workerAdded) {
                //加入成功
                //启动当前线程,将当前线程交有os管理
                t.start();
                //设置标志位
                workerStarted = true;
            }
        }
    } finally {
        //若未启动成功
        if (! workerStarted)
            //回滚当前新起线程操作
            //移除当前新增失败的线程
            //将线程池计数器减1
            //尝试中断线程池或者中断当前线程
            addWorkerFailed(w);
    }
    //返回标志位,是否新增线程成功
    return workerStarted;
}

逻辑图如下:

上图来源于:https://blog.csdn.net/varyall/article/details/82392048

runWorker()方法

Worker中的线程start的时候,调用Worker本身run方法,又调用外部类ThreadPoolExecutor的runWorker方法,runWorker方法代码如下:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread(); // 得到当前线程
    Runnable task = w.firstTask; // 得到Worker中的任务task,也就是用户传入的task
    w.firstTask = null; // 将Worker中的任务置空
    w.unlock(); // allow interrupts。 
    boolean completedAbruptly = true;
    try {
        // 如果worker中的任务不为空,继续执行,
        //如果worker中的任务为空,则使用getTask获得任务。
        //一直死循环,除非得到的任务为空才退出
        while (task != null || (task = getTask()) != null) {
            w.lock();  
            // 在执行任务之前先做一些处理。 
            //1. 如果线程池已经处于STOP状态并且当前线程没有被中断,中断线程 
            //2. 如果线程池还处于RUNNING或SHUTDOWN状态,并且当前线程已经被中断了,重新检查一下线程池状态,如果处于STOP状态并且没有被中断,那么中断线程
            if (
                    (
                     runStateAtLeast(ctl.get(), STOP) //如果线程池已经处于STOP状态或者之后的状态
                     ||
                     (
                        Thread.interrupted()   //本线程已处于中断状态
                        &&
                       runStateAtLeast(ctl.get(), STOP) //再检查一次
                     )
                    ) 
                    &&
                    !wt.isInterrupted()   //worker未状态
               )
                wt.interrupt();
            try {
                beforeExecute(wt, task); // 任务执行前需要做什么,ThreadPoolExecutor是个空实现
                Throwable thrown = null;
                try {
                    task.run(); // 真正的开始执行任务,调用的是run方法,而不是start方法。这里run的时候可能会被中断,比如线程池调用了shutdownNow方法
                } catch (RuntimeException x) { // 任务执行发生的异常全部抛出,不在runWorker中处理
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // 任务执行结束需要做什么,ThreadPoolExecutor是个空实现
                }
            } finally {
                task = null;
                w.completedTasks++; // 记录执行任务的个数
                w.unlock(); // 执行完任务之后,解锁,Worker变成闲置Worker
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly); // 回收Worker方法
    }
}

getTask()方法:

用于缓存队列中获取一个Task。阻塞队列参考:Java并发包--阻塞队列(BlockingQueue)

private Runnable getTask() {
	boolean timedOut = false; // Did the last poll() time out?
 
	retry:
	for (;;) {
		int c = ctl.get();
		int rs = runStateOf(c);
 
		//如果线程池处于shutdown状态,
		//并且队列为空,或者线程池处于stop或者terminate状态,
		//则:线程池数量-1,返回null,回收线程
		if (
            rs >= SHUTDOWN 
            && (rs >= STOP || workQueue.isEmpty())) {
			decrementWorkerCount();
			return null;
		}
 
		//标识当前线程在空闲时,是否应该超时回收
		boolean timed;    
 
		for (;;) {
			int wc = workerCountOf(c);
			//如果allowCoreThreadTimeOut 为ture
			//或者当前线程数量大于核心线程池数目,
			//则需要超时回收
			timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//(1)
			//如果线程数目小于最大线程数目,
			//且不允许超时回收或者未超时,
			//则跳出循环,继续去阻塞队列中取任务(2)
			if (wc <= maximumPoolSize && ! (timedOut && timed))
				break;
			//如果上面if没有成立,则当前线程数-1,返回null,回收该线程
			if (compareAndDecrementWorkerCount(c))
				return null;
			//如果上面if没有成立,则CAS修改ctl失败,重读,cas循环重新尝试修改
			c = ctl.get();  // Re-read ctl
			if (runStateOf(c) != rs)
				continue retry;
			// else CAS failed due to workerCount change; retry inner loop
		}
 
		(2)
		try {
			//如果允许空闲回收,则调用阻塞队列的poll,
			//否则take,一直等到队列中有可取任务
			Runnable r = timed ?
				workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
				workQueue.take();
			//取到任务,返回任务,
			//否则超时timedOut = true;进入下一个循环,
			//并且在(1)处会不成立,进而进入到cas修改ctl的程序中
			if (r != null)
				return r;
			timedOut = true;
		} catch (InterruptedException retry) {
			timedOut = false;
		}
	}
}

processWorkerExit()方法

如果getTask返回的是null,那说明阻塞队列已经没有任务并且当前调用getTask的Worker需要被回收,那么会调用processWorkerExit方法进行回收:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // 如果Worker没有正常结束流程调用processWorkerExit方法,worker数量减一。如果是正常结束的话,在getTask方法里worker数量已经减一了
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); // 加锁,防止并发问题
    try {
        completedTaskCount += w.completedTasks; // 记录总的完成任务数
        workers.remove(w); // 线程池的worker集合删除掉需要回收的Worker
    } finally {
        mainLock.unlock(); // 解锁
    }

    tryTerminate(); // 尝试结束线程池

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {  // 如果线程池还处于RUNNING或者SHUTDOWN状态
        // Worker是正常结束流程的话
        if (!completedAbruptly) { 
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // 不需要新开一个Worker
        }
        
        // 新开一个Worker代替原先的Worker
        // 新开一个Worker需要满足以下3个条件中的任意一个:
        // 1. 用户执行的任务发生了异常
        // 2. Worker数量比线程池基本大小要小
        // 3. 阻塞队列不空但是没有任何Worker在工作
        addWorker(null, false);
    }
}

tryTerminate()方法

在回收Worker的时候线程池会尝试结束自己的运行,tryTerminate方法:

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        // 满足3个条件中的任意一个,不终止线程池
        // 1. 线程池还在运行,不能终止
        // 2. 线程池处于TIDYING或TERMINATED状态,说明已经在关闭了,不允许继续处理
        // 3. 线程池处于SHUTDOWN状态并且阻塞队列不为空,这时候还需要处理阻塞队列的任务,不能终止线程池
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
            
        // 走到这一步说明线程池已经不在运行,阻塞队列已经没有任务,但是还要回收正在工作的Worker
        if (workerCountOf(c) != 0) {
             // 由于线程池不运行了,调用了线程池的关闭方法,在解释线程池的关闭原理的时候会说道这个方法
            interruptIdleWorkers(ONLY_ONE); // 中断闲置Worker,直到回收全部的Worker。这里没有那么暴力,只中断一个,中断之后退出方法,中断了Worker之后,Worker会回收,然后还是会调用tryTerminate方法,如果还有闲置线程,那么继续中断
            return;
        }
        // 走到这里说明worker已经全部回收了,并且线程池已经不在运行,阻塞队列已经没有任务。可以准备结束线程池了
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock(); // 加锁,防止并发
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { // cas操作,将线程池状态改成TIDYING
                try {
                    terminated(); // 调用terminated方法
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0)); // terminated方法调用完毕之后,状态变为TERMINATED
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock(); // 解锁
        }
        // else retry on failed CAS
    }
}

ThreadPoolExecutor的关闭

shutdown方法,关闭线程池,关闭之后阻塞队列里的任务不受影响,会继续被Worker处理,但是新的任务不会被接受:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); // 关闭的时候需要加锁,防止并发
    try {
        checkShutdownAccess(); // 检查关闭线程池的权限
        advanceRunState(SHUTDOWN); // 把线程池状态更新到SHUTDOWN
        interruptIdleWorkers(); // 中断闲置的Worker
        onShutdown(); // 钩子方法,默认不处理。ScheduledThreadPoolExecutor会做一些处理
    } finally {
        mainLock.unlock(); // 解锁
    }
    tryTerminate(); // 尝试结束线程池,上面已经分析过了
}

interruptIdleWorkers方法,注意,这个方法中断的是闲置Worker,中断闲置Worker之后,getTask方法会返回null,然后Worker会被回收。那什么是闲置Worker呢?

闲置Worker是这样解释的:Worker运行的时候会去阻塞队列拿数据(getTask方法),拿的时候如果没有设置超时时间,那么会一直阻塞直到等待阻塞队列进数据,即worker没有获取到任务,这样的Worker就被称为闲置Worker。由于Worker也是一个AQS,在runWorker方法里会有一对lock和unlock操作,这对lock操作是为了确保Worker不是一个闲置Worker。

所以Worker被设计成一个AQS是为了根据Worker的锁来判断是否是闲置线程,是否可以被强制中断。

下面我们看下interruptIdleWorkers方法:

// 调用他的一个重载方法,传入了参数false,表示要中断所有的正在运行的闲置Worker,如果为true表示只打断一个闲置Worker
private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); // 中断闲置Worker需要加锁,防止并发
    try {
        for (Worker w : workers) { 
            Thread t = w.thread; // 拿到worker中的线程
            if (
    !t.isInterrupted() // Worker中的线程没有被中断
&& 
w.tryLock() //并且Worker可以获取锁,这里Worker能获取锁说明Worker是个闲置Worker,在阻塞队列里拿数据一直被阻塞,没有数据进来。如果没有获取到Worker锁,说明Worker还在执行任务,不进行中断(shutdown方法不会中断正在执行的任务)
) { 
                try {
                    t.interrupt();  // 中断Worker线程
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock(); // 释放Worker锁
                }
            }
            if (onlyOne) // 如果只打断1个Worker的话,直接break退出,否则,遍历所有的Worker
                break;
        }
    } finally {
        mainLock.unlock(); // 解锁
    }
}

shutdown方法

将线程池状态改成SHUTDOWN,线程池还能继续处理阻塞队列里的任务,并且会回收一些闲置的Worker。

但是shutdownNow方法不一样,它会把线程池状态改成STOP状态,这样不会处理阻塞队列里的任务,也不会处理新的任务

// shutdownNow方法会有返回值的,返回的是一个任务列表,而shutdown方法没有返回值
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); // shutdownNow操作也需要加锁,防止并发
    try {
        checkShutdownAccess(); // 检查关闭线程池的权限
        advanceRunState(STOP); // 把线程池状态更新到STOP
        interruptWorkers(); // 中断Worker的运行
        tasks = drainQueue();
    } finally {
        mainLock.unlock(); // 解锁
    }
    tryTerminate(); // 尝试结束线程池,上面已经分析过了
    return tasks;
}

shutdownNow的中断和shutdown方法不一样,调用的是interruptWorkers方法:

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); // 中断Worker需要加锁,防止并发
    try {
        for (Worker w : workers)
            w.interruptIfStarted(); // 中断Worker的执行
    } finally {
        mainLock.unlock(); // 解锁
    }
}

Worker的interruptIfStarted方法中断Worker的执行:

void interruptIfStarted() {
   Thread t;
   // Worker无论是否被持有锁,只要还没被中断,那就中断Worker
   if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
       try {
           t.interrupt(); // 强行中断Worker的执行
       } catch (SecurityException ignore) {
       }
   }
}

线程池关闭总结:

线程池的关闭主要是两个方法,shutdown和shutdownNow方法。

shutdown方法会更新状态到SHUTDOWN,不会影响阻塞队列里任务的执行,但是不会执行新进来的任务。同时也会回收闲置的Worker,闲置Worker的定义上面已经说过了。

shutdownNow方法会更新状态到STOP,会影响阻塞队列的任务执行,也不会执行新进来的任务。同时会回收所有的Worker。

参考:

https://fangjian0423.github.io/2016/03/22/java-threadpool-analysis/

猜你喜欢

转载自blog.csdn.net/demon7552003/article/details/91901895