线程池源码分析

本文深入剖析Java线程池的工作原理,从线程的创建与复用、任务执行流程、线程池状态转换及优雅关闭等方面进行详细讲解。通过execute()方法分析任务的提交过程,探讨线程池在不同状态下的任务处理策略,同时阐述了shutdown()与shutdownNow()的区别和任务处理。此外,还揭示了线程池如何通过ctl变量高效地同步线程状态和数量。
摘要由CSDN通过智能技术生成

线程池源码分析主要分两大块,带着下面这些问题来分析

1.线程池的创建:线程池中的线程是如何被创建的?任务是怎么被执行的。执行完任务后是如何复用的。当任务挤压后,又是如何处理这些任务的?主要以execute()为切入点展开

2.线程池的销毁:如何优雅的关闭掉一个线程池,如果关闭线程池,那正在执行的任务、以及等待被执行的任务将如何被处理,还要如果关闭线程池后,还继续提交任务,线程池如何处理呢?主要是以shutdown()shutdownNow()为切入点展开

1.任务是如何被添加并被执行的

1.线程池的状态位和线程数量表示方式
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

//29
private static final int COUNT_BITS = Integer.SIZE - 3;
//一共29个1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

// Packing and unpacking ctl
//获取线程池的状态rs
private static int runStateOf(int c)     { return c & ~CAPACITY; }
//获取线程池中的线程数量
private static int workerCountOf(int c)  { return c & CAPACITY; }
//初始化变量ctl,使用两个数或操作初始化,高3位表示状态,低29位表示线程池中线程数量
private static int ctlOf(int rs, int wc) { return rs | wc; }

这里的设计是这样的,使用一个int类型变量ctl中的高3位表示线程池的状态位和低29位当前线程池中的线程数量

2.线程池的状态
  • RUNNING 表示当前线程处于运行状态,可以接受新提交任务
  • SHUTDOWN 不接受新任务,但是会执行完阻塞队列中待执行的任务,一般是通过shutdown()产生
  • STOP 不接受新任务,阻塞队列中待执行任务也不执行,一般是通过shutdownNow()产生
  • TIDYING 线程池中所有的任务已经结束,线程数量为0,开始调用terminated() 临时状态
  • TERMINATED terminated()方法调用完成
3.提交任务

在分析之前,先记住一下几点,

1.线程池是在多线程环境下操作的,所以代码中要考虑并发问题。

2.核心线程数是可以被设置超时时间的,任务队列中阻塞超过一定时间就可以被中断。也就说可以设置成没有活跃的核心线程数,但线程池状态还是running,但是核心线程数为0,PS:使用allowCoreThreadTimeOut(boolean value) 来设置运行设置,

3.线程池源码很多操作都是先假设成功,后续如果失败,然后再回滚前面的操作,比如添加任务创建线程,一开始就把任务加到队列中,如果后面条件不满足,又把任务从队列中移除掉

 public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //判断线程池中线程数是否小于核心线程数,需要创建核心线程来执行任务
        if (workerCountOf(c) < corePoolSize) {
            //添加任务
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //执行这里,说明前面核心线程数已经满了,如果线程池还处于running状态,则尝试将任务放入阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //这里是什么场景,为啥需要重新检查线程池状态?可能出现刚刚把任务放入线程池中的任务队列,然后此时线程池被关闭了,那么此时需要把任务从队列中移除(并发情况)
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //这里是什么场景?核心线程数为0了?这就是前面说的我们可以设置核心线程数完成任务后就被销毁,那么核心线程数就为0了,那么刚刚队列中的任务怎么执行呢,就需要使用使用创建非核心线程数来执行任务了(可以忽略,因为不会这么设置)
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
       //执行到这里,说明核心线程数满了,队列也满了,创建非核心线程来执行任务
        else if (!addWorker(command, false))
            reject(command);
    }

好,抛开线程池里面任务添加细节逻辑,我们大致知道了一个任务被加入到线程池时处理逻辑了

  1. 判断当前线程数是否小于核心线程数,如果小于则创建核心线程,执行任务。否则执行第2步
  2. 如果线程池还处于运行状态,那么将任务添加到队列中,如果添加成功,执行下面操作,否则执行3步
    1. 再次判断线程池状态,如果现在是非运行状态则将刚刚添加到队列中的任务移除掉
    2. 判断线程池中线程数量,如果数量为0,则新增非核心线程数(忽略这种情况,正常情况下不会配置核心线程数可以被销毁的情况
  3. 新增非核心线程数来执行任务,如果失败,则使用拒绝策略处理任务

下面开始详细分析addWorker()

/**
**  firstTask 表示要被执行的任务,如果为空,那么表示从队列中取任务执行,否则就取firstTask执行
**  core 是否创建核心线程还执行任务
**/
private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
    // 下面这个for()主要是做校验,如果校验通过就把线程池中的线程数量+1,注意,这里仅仅只是数量更新,但实际真正的线程还没有被创建
        for (;;) {
            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;
                //走到这里说明前面cas失败了,那么需要重新读取
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
		//下面开始创建线程,然后使用新创建的线程来处理任务
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new 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()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //将work添加到线程池集合中
                        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;
    }

我们一段段分析

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

前面rs >= SHUTDOWN 满足的话说明当前线程池至少是处于shutdown状态,注意这种状态是不能再执行添加新任务的,

!(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())含义是说

  1. 线程池处于shutdown状态
  2. 新增的任务为空(说明从任务队列中拿出任务执行)
  3. 任务队列不为空

这三个里面只要有一个不满足,那整个if()就返回true,代表任务添加失败,其实就是

  1. 表示当前线程池处于SHUTDOWN 状态后,新增的任务不为空的,直接返回,代表添加任务失败
  2. 表示当前线程池处于SHUTDOWN 状态后,核心线程数为0,新增非核心线程数来处理任务,但是队列为空,直接返回,代表添加任务失败.
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;
}
  • 如果当前线程数已经达到最大了CAPACITY了,直接返回
  • 如果添加的是核心线程,但当前线程数据已经达到core了直接返回
  • 如果添加的的非核心线程数,但当前线程数已经达到maximumPoolSize直接返回

前面校验都通过了,开始新增线程池中的线程数量compareAndIncrementWorkerCount(c)通过CAS添加如果失败,进行重试,否则直接跳出外面最大的循环。

break retry 跳出到最外层的循环 单纯的break只能跳出当前循环

将传入的task任务封装成worker对象,创建worker对象其实就是通过线程工厂创建了一个线程

 w = new Worker(firstTask);
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    // ... 省略部分代码
    Worker(Runnable firstTask) {
          setState(-1); // inhibit interrupts until runWorker
          this.firstTask = firstTask;
          this.thread = getThreadFactory().newThread(this);
    }

    public void run() {
        runWorker(this);
    }
}

下面一系列的操作,最终会调用t.start(); 启动线程,执行任务,也就是间接调用runWorker(this);

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        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 {
                        //还是调用了传入的任务的run()
                        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);
        }
    }

上面的代码中有个关键点completedAbruptly = false;什么时候不会被执行?因为这里没有catch,所以

  • task.run();发生异常
  • beforeExecute(wt, task); 发生异常
  • afterExecute(task, thrown); 发生异常

为啥getTask()发生异常没有提到,因为这里面代码实现被try...catach捕获了,不会对外抛出异常。(后面后详细讲这里的实现)

所以一言以蔽之,completedAbruptly = false;只有在用户自己的任务出现异常抛出导致的,手动关闭线程池不会受到影响

获取任务getTask()详细分析

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
            int wc = workerCountOf(c);
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
          	//如果允许核心线程超时且已经超时且队列中任务为空则直接减少线程数据退出死循环返回空
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

前面都是一些校验,最关键的代码

try {
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
    workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    timedOut = false;
}
  1. 这里是线程复用的关键,如果设置了允许核心线程超时,workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)那么超时返回。线程不再被复用
  2. 如果未设置超时,则一直阻塞等待workQueue.take()
  3. 无论上面哪种情况,都是支持中断退出的。关于如何中断线程池,后面会详细讲述

然后我们看下最终要执行的代码processWorkerExit(w, completedAbruptly);里面的逻辑(这段代码执行的前提是任务出现异常,或者线程被中断跳出等待任务队列才会执行,否则会一直阻塞在从任务队列获取任务

/**
* completedAbruptly 如果是true表示是任务代码中出现了异常
**/
private void processWorkerExit(Worker w, boolean completedAbruptly) {
       // 如果出现异常,则将线程池中线程数量-1
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        tryTerminate();

        int c = ctl.get();
        //如果当前线程处于运行running 或者shutdown状态
        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);
        }
    }

上面代码可以解决我们前面的疑问

  1. 线程池中的线程是如何复用的? while (task != null || (task = getTask()) != null)

    我们传入的任务如果不为空,那么就执行传入的任务,执行完成后,就从队列中拿任务执行(task = getTask())。如队列中为空,那么就会阻塞,线程不会被销毁。

  2. 如果我们提交的任务抛异常了,线程池中的线程会被终止销毁吗?

    如果出现任务异常,原线程结束,会新启一个新的线程,也就是线程池中的线程数量不会受到任务异常的影响。

4.关闭线程池
  • shutdown() 不接受新任务,但是执行队列中的任务

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            //修改线程池状态位SHUTDOWN
            advanceRunState(SHUTDOWN);
            //中断空闲线程,何为空闲线程?
            interruptIdleWorkers();
            //钩子方法,等待子类实现,默认为空方法
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    

    然后我们看下中断空闲线程方法的实现

    private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
    
    private void interruptIdleWorkers(boolean onlyOne) {
            final ReentrantLock mainLock = this.mainLock;
          
            mainLock.lock();
            try {
                for (Worker w : workers) {
                    Thread t = w.thread;
                      //这里很关键,能获取到锁,说明这个线程是空闲线程
                    if (!t.isInterrupted() && w.tryLock()) {
                        try {
                            t.interrupt();
                        } catch (SecurityException ignore) {
                        } finally {
                            w.unlock();
                        }
                    }
                    if (onlyOne)
                        break;
                }
            } finally {
                mainLock.unlock();
            }
        }
    
    

    这里是中断空闲线程,那么如何区分出哪些是空闲线程呢。通过w.tryLock(); 能获取锁成功的,表示是空闲线程,因为如果线程在支线任务时,也必须获取到锁,而处于阻塞等待的线程是没有获取到锁的。

    那么问题来了,这里只是把空闲线程中断停了,那么那些非空闲线程执行完任务后,是如何停止的呢?

    正常线程执行完任务后,就会getTask()阻塞、这里面有段代码。当线程池状态为SHUTDOWN且任务队列为空,则直接返回空,这样线程就能正常退出来了

     private Runnable getTask() {
         boolean timedOut = false; // Did the last poll() time out?
    
         for (;;) {
             int c = ctl.get();
             int rs = runStateOf(c);
    
              //这里就能跳出循环
             if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                 decrementWorkerCount();
                 return null;
             }
                ...
    }
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值