execute 核心流程的源码分析

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    //线程池状态 高3位表示线程状态 低29位代表线程数量
    int c = ctl.get();
    //判断当前线程池线程数量是否小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
        //作为核心线程数进行线程的创建,并且创建成功线程会将command的任务执行--》对应图上的直接执行
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //创建核心线程失败或者当前线程数量超过核心线程数
    //当前线程池是否还在运行状态,尝试将任务添加到阻塞队列 --》对应图上的缓冲执行
    //BlockingQueue队列的顶级抽象定义了offer不是进行阻塞添加而是立即返回,添加失败直接返回false,区别于put
    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);
    }
    //到这里那就是添加队列失败,或者线程池状态异常,但是这里仍然尝试进行创建一个worker
    //如果创建失败,也是走拒绝策略拒绝当前任务
    else if (!addWorker(command, false))
        reject(command);
}

接下来我们仔细看看 addWorker 这个方法具体是在做什么:

//核心逻辑其实就是在无限循环创建一个worker,创建失败直接返回,创建成功,则将worker执行
// 因为worker有thread的成员变量,最终添加worker成功,会启动线程的start方法
//start方法最终会回调到外层的runWorker方法,改方法会不停的从阻塞队列里以阻塞的take方式
//获取任务,除非达到能被终止的条件,此时当前线程会终止
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
        
        // Check if queue empty only if necessary.
        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;
            //不停的重试添加worker的计数,只有添加成功的才会进行后续的worker启动
            if (compareAndIncrementWorkerCount(c))
                break retry;
            c = ctl.get();  // Re-read ctl
            //重试期间,如果其他线程导致线程池状态不一致了。重新回到第一个循环进行check判断
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
    
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            //这里加锁一个是workers.add时需要加锁,另外是防止其他线程已经在尝试修改线程池状态了
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                int rs = runStateOf(ctl.get());
                
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    //将worker的引用添加到workers的hashSet中
                    workers.add(w);
                    int s = workers.size();
                    //更新线程池此时最大的线程数
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            //如果添加成功,就启动worker中的线程
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        //这里添加失败的话,需要把线程池的count数进行--,并且要把worker引用从hashSer中移除
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

3.4 线程池的生命周期

在介绍运行机制原理的源码分析时,其实是有提到线程池状态这个概念的。介绍这个状态其实也是让大家更方便的去管理线程池,比如我们关闭线程池时,怎么去优雅的关闭,使用不同的方法可能会有不同的效果,我们需要根据自己的业务场景去酌情分析、权衡使用。

//线程池的状态和计数采用一个Integer变量设置的
//这里之所以用一个变量来储存状态和数量,其实很有讲究的,因为我们在上面的运行原理上可以看到
//源码中有大量的进行状态以及数量的判断,如果分开采用变量的记录的话,在维护二者一致性方面
//可能就需要加锁的维护成本了,而且计算中都是位移运算也是非常高效的
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程池的大小由ctl低29位表示,现成状态由ctl高3位表示
private static final int COUNT_BITS = Integer.SIZE - 3;
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;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;


// 这里是获取线程状态以及获取线程数量的简单高效的位移方法
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }

接下来结合源码详细介绍下线程池的 5 种状态以及分别有什么不同的表现行为?

先说下结论:


RUNNING     这个就是线程池运行中状态,我们可以添加任务也可以处理阻塞队列任务


SHUTDOWN   不能添加新的任务,但是会将阻塞队列中任务执行完毕


STOP       不能添加新的任务,执行中的线程也会被打断,也不会处理阻塞队列的任务


TIDYING    所有线程都被终止,并且workCount=0时会被置为的状态


TERMINATED   调用完钩子方法terminated()被置为的状态 

shutdown 状态源码分析:

 
 //线程池关闭
 public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        //循环cas设置线程池状态,直到成功或状态已经state>=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;
            //worker的线程没有被打断过,并且能获取到worker的aqs独占锁
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    //打断当前线程,如果线程在阻塞队列中阻塞,此时会被中断
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

STOP 状态分析

//循环cas修改线程池状态为stop。打断所有线程,取出阻塞队列的所有任务
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //检查线程的权限
        checkShutdownAccess();
        //将状态case为stop
        advanceRunState(STOP);
        //打断所有worker不管是不是正在执行任务
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}


//这里获取锁之后。打断了所有的线程
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

TIDYING、TERMINATED 状态分析

//这个方法在每个线程退出时都会进行调用,如果是运行中、或者状态大于等于TIDYING或者shutdown但是队列不为空都
//直接返回,如果不满足以上条件,并且线程数不为0的话,打断一个空闲线程
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }


        //此时到这里,状态要么为STOP。要么是shutdown并且队列为空了
        // 获取一个锁,尝试cas修改状态为TIDYING
        //调用terminated()的钩子方法,
        //修改线程池为终态TERMINATED,并且唤醒阻塞在termination队列上的线程
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

四、JDK 内置线程池的问题

java.util.concurrent.Executors 工厂类提供了一些静态方法,方便我们快速创建几种预设配置的线程池:

  • Executors.newFixedThreadPool(int nThreads):
    • 创建一个固定大小的线程池。corePoolSize 和 maximumPoolSize 都等于 nThreads。
    • keepAliveTime 为 0L(因为线程数不会超过 corePoolSize,所以此参数无效,除非 allowCoreThreadTimeOut 为 true)。
    • 使用无界的 LinkedBlockingQueue 作为工作队列。
    • 问题:由于使用无界队列,当任务提交速度远大于处理速度时,队列会持续增长,可能导致内存溢出(OOM)。此时 maximumPoolSize 参数实际上是无效的,线程数永远不会超过 nThreads。

  • Executors.newSingleThreadExecutor():
    • 创建一个只有一个工作线程的线程池。corePoolSize 和 maximumPoolSize 都为 1。
    • 同样使用无界的 LinkedBlockingQueue。
    • 保证所有任务按照提交顺序(FIFO)执行。
    • 问题:与 newFixedThreadPool 类似,无界队列可能导致 OOM。

  • Executors.newCachedThreadPool():
    • 创建一个可缓存的线程池。
    • corePoolSize 为 0。
    • maximumPoolSize 为 Integer.MAX_VALUE (几乎是无界的)。
    • keepAliveTime 为 60 秒。
    • 使用 SynchronousQueue 作为工作队列。这种队列不存储元素,任务提交后必须有空闲线程立即接收,否则会创建新线程(如果未达到 maximumPoolSize)。
    • 问题:如果任务提交速度过快,会创建大量线程(理论上可达 Integer.MAX_VALUE 个),可能耗尽系统资源,导致 OOM 以及频繁的上下文切换。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值