【并发】Java并发线程池底层原理详解与源码分析(下)
【面试题】 execute()方法与submit()方法有什么区别???
ThreadPoolExecutor 之 execute()方法详解
ThreadPoolExecutor 之 addWorker()方法详解
ThreadPoolExecutor 之 runWorker()方法详解
【并发】Java并发线程池底层原理详解与源码分析(下)
前情回顾
上篇文章地址
遗留问题解析
手动实现线程池代码
public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 自定义线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
                0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));
        for (int i = 1; i <= 100; i++) {
            threadPoolExecutor.execute(new MyTask(i));
        }
    }
}
 
class MyTask implements Runnable {
    int i = 0;
 
    public MyTask(int i) {
        this.i = i;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
        try {
            Thread.sleep(3000L);//业务逻辑
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}运行结果

我们在上一个章节中提到了手动实现线程池,但是它的结果似乎有点匪夷所思!
为什么11~20会在最后面?
为什么运行会抛异常?
为什么打印日志只有前30个?
!!!先说结论!!!
在案例代码中,有100个任务,1个任务的处理时间大致需要3秒,10个核心线程,10个临时线程,阻塞队列的容量是10。
这里只会打印会前30个任务(10+10+10=30),由于在3s内核心线程和临时线程都在忙碌中,队列也满了,按照ThreadPoolExecutor默认的策略会抛出异常!
按照线程池的工作顺序,会先分配10个核心线程(1~10),再装满队列(11~20),最后分配临时线程(21~30);执行逻辑是核心线程和临时线程会先把“手头上”的任务处理完,才会去处理队列里的任务,这就是队列里的任务(11~20)最后打印的原因!!!
图解 ThreadPoolExecutor
我们可以先来研究一下,线程池的逻辑结构以及它的执行流程,以便于我们可以更加轻松的看懂源码!

下图是线程池接收任务的顺序
先装满核心线程 -> 再装满队列 -> 最后是临时线程

ThreadPoolExecutor 运行完整的流程

ThreadPoolExecutor 源码分析
接下来,我将会结合线程池ThreadPoolExecutor的源码来解析这个问题!!!
线程池源码结构

顶层接口是Executor,它只有一个方法execute();ExecutorService接口继承了Executor接口,它主要是提供了submit()。
【面试题】 execute()方法与submit()方法有什么区别???
submit的底层还是调用了execute
(1)关于返回值的问题
- submit:有返回值,返回值(包括异常)被封装于- FutureTask对象。适用于有返回结果的任务。
- execute:- void类型的函数,没有返回值,适用于没有返回的任务。
(2)关于异常处理的问题
- submit:- submit的时候并不会抛出异常(此时线程可能处于就绪状态)。只有在- get操作的时候会抛出。因为- get操作会阻塞等待线程的执行完毕。
- execute:在执行的时候会直接抛出。可以通过实现- UncaughtExceptionHandler接口来完成异常的捕获。
ThreadPoolExecutor 变量详解
// 记录线程池的状态信息,高三位是状态信息,其余位表示工作的worker数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 表示worker数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// worker容量
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 可以接受新的任务,也可以处理队列中的任务
private static final int RUNNING    = -1 << COUNT_BITS;
// 不接受新的任务,但是可以处理队列里的任务
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 后面3个都不行!!!
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;ThreadPoolExecutor 之 execute()方法详解
由于 ThreadPoolExecutor 类封装得很好,所以exectue()看起来很简单,只有短短的几行代码。
这几行代码归纳起来其实只有4个步骤!
public void execute(Runnable command) {
    // 1. 任务非空校验
    if (command == null)
        throw new NullPointerException();
    // 2. 添加核心线程执行任务
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 3. 任务入队(队列)
    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);
    }
    
    // 4. 添加临时线程执行任务,如果失败则执行拒绝策略 
    else if (!addWorker(command, false))
        reject(command);
}看到这里,相信大家应该可以将其与上文中提到的任务提交优先级联系起来!
先装满核心线程 -> 再装满队列 -> 最后是临时线程
除去第一步,校验是否为空值的步骤外,实际上核心的步骤也就3步!
注:3个步骤中都有 addWorker(X,X) 的操作!但是它们参数都不同,所以它们代表的含义也各不相同,稍后会详解这个方法!
(1)添加核心线程执行任务
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    // 如果addWorker失败,则ctl要重新获取。
    // 因为不管是状态变,还是worker数量变,ctl都已经变了,你需要重新获取最新值
    c = ctl.get();
}// 初始值 CAPACITY = 536870911
// 00011111 11111111 11111111 11111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
// c的初始值 -536870912
// 转换位二进制就是 11100000000000000000000000000000
// 11100000000000000000000000000001(+1)
// 11100000000000000000000000000010(+1)
private static int workerCountOf(int c)  { return c & CAPACITY; }int是32位了,这里用高3位表示状态,低29位表示线程数量!
workerCountOf(c)用于计算当前线程池中正在工作的线程数,如果计算的数量大于等于核心线程数,则会执行后面的逻辑!
(2)任务入队(队列)
在当前worker数量大于等于corePoolSize 或者 上面的addWorker()失败之后才会走到这里!
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);
}private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}
boolean offer(E e);查看一下当前线程的状态是否是running状态;如果是,则会执行offer()操作,尝试往工作队列里面添加这个任务,但是有可能失败(工作队列可能满了)
public boolean remove(Runnable task) {
    boolean removed = workQueue.remove(task);
    tryTerminate(); // In case SHUTDOWN and now empty
    return removed;
}重新获取一次ctl的值,做二次确认;如果此时状态不是running,就对之前的任务执行remove()操作,并抛异常!
else if (workerCountOf(recheck) == 0)
    addWorker(null, false);当代码走到这里的时候,说明之前的offer()操作肯定是成功了, 这个判断是为了防止没有worker,但是队列里面有任务,没人执行!!!
addWorker(null, false),这个方法执行时只是创建了一个新的线程,但是没有传入任务,这是因为前面已经将任务添加到队列中了,这样可以防止线程池处于 running 状态,但是没有线程去处理这个任务。
(3)添加临时线程执行任务,如果失败则执行拒绝策略
else if (!addWorker(command, false))
    reject(command);执行到这里,说明核心线程此时都在工作,并且队列也满了!现在要申请让临时线程开始工作。
【问】这里为什么需要二次确认???
在多线程环境下,线程池的状态时刻在变化,而 ctl.get() 是非原子操作,很有可能刚获取了线程池状态后线程池状态就改变了。判断是否将 command 加入 队列是线程池之前的状态。倘若没有 二次确认,万一线程池处于非 running 状态(在多线程环境下很有可能发生),那么 command 永远不会执行。
ThreadPoolExecutor 之 addWorker()方法详解
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        // 每次for循环都需要获取最新的ctl值
        int c = ctl.get();
        
        // 获取这个线程的状态
        int rs = runStateOf(c);
        if (rs >= SHUTDOWN &&
                !(rs == SHUTDOWN &&
                    firstTask == null &&
                    !workQueue.isEmpty()))
            return false;
        for (;;) {
            int wc = workerCountOf(c);
            if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
        }
    }
    boolean workerStarted = false;
    boolean workerAdded = false;
    ThreadPoolExecutor.Worker w = null;
    try {
        w = new ThreadPoolExecutor.Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}addWorker(Runnable firstTask, boolean core)
这里面有两个入参 firstTask、core
- firstTask 即是当前添加的线程需要执行的首个任务.
- core 用来标记当前执行的线程是否是核心线程
上述代码在exectue()中被调用了3次,但是每次入参都不同!
- addWorker(command, true):开启核心线程执行任务
- addWorker(null, false) :创建一个线程,从队列中获取任务
- addWorker(command, false):开启临时线程来执行任务
代码比较长,我们将代码分成两个部分来看
(1)goto部分 做“check”
retry:
for (;;) {
    int c = ctl.get();
    // 获取当前线程的状态
    int rs = runStateOf(c);
    // 不需要临时线程
    // 相当于rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())
    if (rs >= SHUTDOWN &&
        ! (rs == SHUTDOWN &&
           firstTask == null &&
           ! workQueue.isEmpty()))
        return false;
    for (;;) {
        // 判断容量
        int wc = workerCountOf(c);
        if (wc >= CAPACITY ||
            wc >= (core ? corePoolSize : maximumPoolSize))
            return false;
	    // 尝试通过CAS方式,如果前面没有问题,这里自增+1
        if (compareAndIncrementWorkerCount(c))
            // 自增后退出循环,执行后面逻辑!
            break retry;
        c = ctl.get();  
        // 如果线程池状态发生变化,重新从最外层循环
        if (runStateOf(c) != rs)
            continue retry;
    }
}
判断是否要开临时线程
if (rs >= SHUTDOWN && 
        !(rs == SHUTDOWN && 
          firstTask == null &&
          !workQueue.isEmpty()))
        return false;这个代码有一点绕!翻译过来其实就是:
rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())
- rs > shutdown:线程池状态处于STOP,TIDYING,TERMINATED时,添加工作线程失败,不接受新任务
- rs >= shutdown && firstTask != null:线程池状态处于 SHUTDOWN,STOP,TIDYING,TERMINATED状态且worker的首个任务不为空时,添加工作线程失败,不接受新任务。
- rs >= shutdown && workQueue.isEmppty:线程池状态处于 SHUTDOWN,STOP,TIDYING,TERMINATED状态且阻塞队列为空时,添加工作线程失败,不接受新任务。
当满足这3种情况的时候,就不要 “加 Worker” 了!!!
判断容量
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
    wc >= (core ? corePoolSize : maximumPoolSize))
    return false;- 工作线程数量是否超过可表示的最大容量(CAPACITY)
- 如果添加核心线程,是否超过最大核心线程容量(corePoolSize)
- 如果添加临时线程,是否超过线程池最大线程容量(maximumPoolSize)
如果容量符合“标准”,就会执行后面的CAS操作;如果CAS执行成功,退出循环,进入第二阶段!
private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}(2)“干事情”
// 注意 Worker代表线程,Task表示任务
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
    //创建Worker对象实例
    w = new Worker(firstTask);
    //获取Worker对象里的线程
    final Thread t = w.thread;
    if (t != null) {
        //开启可重入锁,独占
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //获取线程池运行状态
            int rs = runStateOf(ctl.get());
            //满足 rs < SHUTDOWN 判断线程池是否是RUNNING,或者
            //rs == SHUTDOWN && firstTask == null 线程池如果是SHUTDOWN,
            //且首个任务firstTask为空,
            if (rs < SHUTDOWN ||
                (rs == SHUTDOWN && firstTask == null)) {
                if (t.isAlive()) 
                    throw new IllegalThreadStateException();
                //将Worker实例加入线程池workers
                workers.add(w);
                int s = workers.size();
                if (s > largestPoolSize)
                    largestPoolSize = s;
                //线程添加成功标志位 -> true
                workerAdded = true;
            }
        } finally {
            //释放锁
            mainLock.unlock();
        }
        //如果worker实例加入线程池成功,则启动线程,同时修改线程启动成功标志位 -> true
        if (workerAdded) {
            // 执行 start 会调 run方法!!!
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        //添加线程失败 回滚
        addWorkerFailed(w);
}
return workerStarted;
最核心的部分如下所示
try {
    int rs = runStateOf(ctl.get());
    if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
        // 当前线程还没有启动,但是它却是存活状态,就会抛异常!!!
        if (t.isAlive()) 
            throw new IllegalThreadStateException();
        workers.add(w);
        int s = workers.size();
        if (s > largestPoolSize)
            largestPoolSize = s;
            workerAdded = true;
    }
} finally {
    mainLock.unlock();
}- 首先检查线程池的状态,当线程处于 RUNNING 状态 或者 线程处于 SHUTDOWN 状态且当前线程的 firstTask 为空,满足以上条件时才能将 worker 实例添加进线程池,即workers.add(w);
- 同时修改 largestPoolSize,largestPoolSize变量用于记录目前最大线程数。
- 将标志位 workerAdded 设置为 true,表示添加线程成功。
- 无论成功与否,在 finally 中都必须执行 mainLock.unlock()来释放锁。
if (workerAdded) {
    t.start();
    workerStarted = true;
}当上面的条件都满足的时候,会执行 start() 方法,会调 run方法!!!
ThreadPoolExecutor 之 runWorker()方法详解
在线程池中,其实还有两个概念——提交优先级 和 执行优先级
上面的源码分析,其中 exectue() 和 addWorker() 是提交部分;而这里的 runWorker() 是执行部分
在上面的addWorker()方法中调了start()方法,JVM会通过 start()来调用run()方法!!!自然最后会调到runWorker()
if (workerAdded) {
    // 执行 start 会调 run方法!!!
    t.start();
    workerStarted = true;
}
public void run() {
    runWorker(this);
}final void runWorker(ThreadPoolExecutor.Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); 
    boolean completedAbruptly = true;
    try {
        // 执行优先级!!!
        while (task != null || (task = getTask()) != null) {
            w.lock();
            if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                            runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                wt.interrupt();
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    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);
    }
}执行优先级源码实现
while (task != null || (task = getTask()) != null) {...}为什么说这里可以将执行优先级体现得“淋漓尽致”呢???
这个task表示任务,当 task 为null的时候,才会执行 getTask() 即从队列中获取任务,并交给线程处理!

这里又一次的说明了 addWorker(null,false) 的作用!!!
所以,总的来说,这里的逻辑是核心线程和临时线程先处理完自己手头上的任务后,才会去线程池里拿!!!
beforeExecute(wt, task);
afterExecute(task, thrown);
上面的代码中可以看到有beforeExecute、afterExecute,它们都是钩子函数,可以分别在子类中重写它们用来扩展 ThreadPoolExecutor,例如添加日志、计时、监视或者统计信息收集的功能。
线程复用机制源码实现
processWorkerExit(w, completedAbruptly);
private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) {
    if (completedAbruptly) 
        decrementWorkerCount();
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; 
        }
        // 线程复用!!!
        addWorker(null, false);
    }
}在 runWorker() 代码的最后的finally里面,会调 processWorkerExit() 方法,这个方法非常的重要!因为线程池的复用机制就是在这里体现的!!!
最后这里调了 addWorker(null, false) 表示会创建一个新的线程(但是没有任务),其实就是表示当前线程将自己之前手头上的活处理完了,现在又可以接活了!!!
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号