深入理解Java线程池(2):ThreadPoolExecutor执行任务相关方法,内部类worker

这次来看看worker线程启动和运行的相关方法。了解任务获取与异常处理机制。
关于线程池参数解释,启动方法解析请移步:
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83549068

Worker

先说说Worker类,它作为线程的承载类、任务的执行者,自然少不了实现Rannable接口,在ThreadPoolExecutor的addWorker被执行后会通过Thread中的start方法开启worker线程,而内部run方法则会调用ThreadPoolExecutor的runWorker方法来执行任务。

不仅如此,worker还继承了AbstractQueuedSynchronizer(AQS)类,能够使用非重入锁。不同于ReentrantLock,AQS在保证了多个线程并发安全的同时,还拒绝了同一个线程的多次重入。使用AQS有一下两点原因:

  • 当worker被加锁,则表示worker线程正在执行任务,该线程不应该被中断,且一个worker线程不应该同时执行两个任务。
  • 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态(setCorePoolSize等方法也通过trylock来判断)。如果worker正在执行任务则其已经被加锁,此时trylock方法不能成功,进程不会被中断。保证了shutdown不会杀死有任务的worker线程。
注意:加锁为1;解锁为0;初始值为-1表示尚未启动

以下就是Worker代码

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    private static final long serialVersionUID = 6138294804551838833L;
    //成功创建worker,thread不可能为空
    final Thread thread;
    //起始任务,可能为空
    Runnable firstTask;
    //记录完成任务个数
    volatile long completedTasks;

   //初始化方法,state为0,新建线程。
    Worker(Runnable firstTask) {
        setState(-1); // 防止启动之前就中断worker中的线程
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this);
    }

//正式启动,在runWorker方法中循环读取任务。
    public void run() {
        runWorker(this);
    }
    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()) {	//getState>=0,0就是解锁,1就是加锁
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

runWorker

worker被调用start方法后,线程变为启动状态,之后线程启动后run方法会调用runWorker。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    //设置worker变为解锁状态
    w.unlock(); 
	//任务未被完整执行,出现异常
    boolean completedAbruptly = true;
    try {
		//任务不为空或者任务队列中任务不为空,则开始执行任务。
		//添加worker时可能添加没有task任务的worker
        while (task != null || (task = getTask()) != null) {
			//根据每一个worker开启AQS锁
            w.lock();

  		//下面的逻辑是‘worker线程中断逻辑’,具体逻辑解释将会在代码下面找到
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);	//空方法
                Throwable thrown = null;
                try {
                //会由于任务抛出异常而导致worker关闭。会在下边processWorkerExit方法重新创建。
                    task.run();						
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                	//空方法
                    afterExecute(task, thrown);
                }
            } finally {
			//解锁,以及善后工作
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
		//用于最后的收尾工作
        processWorkerExit(w, completedAbruptly);
    }
}
  • ‘worker线程中断逻辑’解释:
    其根本原因还是shutdown和stop状态变更的问题。
    运行到此处说明已经获取了任务。如果此时状态为stop或级别更高(如tidying),worker线程应该被中断;而如果此时是shutdown状态则worker不能停止运行。
    所以,stop状态会进入if方法体,执行interrupt方法;而shutdown状态不仅不会进入方法体,还会被interrupted方法清空线程中的中断状态,保证线程在执行任务时不会由于阻塞而退出任务。(这部分线程中断退出常识,请自行查阅)
    至于多次使用runStateAtLeast方法进行判断,实际上保证了worker状态和线程状态的一致性,shutdownNow方法会先改变worker状态,后改变线程状态。

  • processWorkerExit方法调用场景:
    1)终止线程池时,用于从线程集合中移除工作线程;
    2)当前线程因为异常而退出,重新创建线程替换之;
    3)线程池中因为设置allowCoreThreadTimeOut=true,导致工作线程全部被回收时,任务队列仍然有任务,则新建线程;
    可能还有其他情况,此方法会在后边继续分析。

getTask

从队列里获取任务的方法,如果任务队列为空,会在此处进行阻塞

private Runnable getTask() {
    boolean timedOut = false; // 上次是否超时
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        //1 线程池状态是stop或以后。2 状态为shutdown且队列为空。
		//在以上两个情况下,worker数量减一后推出
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
        int wc = workerCountOf(c);
        //是否进行超时控制,超过核心线程池数量之后,或者,允许核心线程超时,都一定会开启
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		//超时控制被触发,会worker数量减一,返回空;触发失败从新循环判断。
		//触发标准,(1||2)&&(3||4)
		//1 worker数量大于最大线程池数量
		//2 进行超时控制并且上次获取任务发生了超时
		//3 worker数量大于1
		//4 任务队列为空
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
		//超时控制时,若获取任务超时则在timedOut=true
		//以下逻辑是‘阻塞队列获取任务’
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) 
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;	//阻塞队列特性,若特定时间获取不到,判定为超时。
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}
  • 关于‘阻塞队列获取任务’:
    首先,在队列阻塞时候,若由于shutdown方法会中断正在阻塞的方法并抛出异常,trycatch会保证此worker不会被意外终止,而是重新进行循环判断。
    其次,无论是由于线程中断抛出异常,还是没有获取到任务,都会使timedOut标志位变成false。下次进行循环时,会根据当前线程池状态等诸多因素决定是否退出。
  • 退出线程池的几种情况:(默认最大线程池数量>核心线程池数量>1,不考虑极端情况)
    1 worker线程数量大于设置的线程池最大容量。
    2 worker线程数量大于设置的核心线程数量,且发生了超时。
    3 worker线程数量小于设置的核心线程数量,但大于1,并且,线程池设置了允许核心线程关闭的变量。
    4 worker线程数量等于1,任务队列为空。
    以上几种情况是我认为非极端情况下发生的worker线程自动退出的情况。

processWorkerExit

在runWorker方法执行到最后被调用,处理单个worker结束后“善后工作”
入参w可以理解为是当前worker线程,completedAbruptly表示是否是非正常情况下退出。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
	//completedAbruptly为true,属于worker非正常退出,线程池数量减一
    if (completedAbruptly) 
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
		//添加进总完成线程数
        completedTaskCount += w.completedTasks;
		//移除worker
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
	//判断是否需要结束线程池
    tryTerminate();

    int c = ctl.get();
//下边这段代码是用来判断是否需要添加worker的。
//首先线程池状态必须为running或shutdown,且满足以下两点任意一点
//1 该worker是非正常退出
//2 虽然该worker是正常退出,但是当前线程数小于等于规定线程数min(规定线程数,根据代码中的情况进行判断)
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);					//addworker方法仅仅是尝试增加
    }
}
  • 关于最后的添加worker逻辑:
    能运行到这里说明当前线程肯定是没救了(要关闭了)。但是,在某些情况下,线程池肯定是不希望自己少一个线程的。所以它在最后加了一个重启一个新的worker线程的操作,希望弥补worker线程资源的不充足。

最后附上深入理解Java线程池其他文章链接:
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83549068
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83717842

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值