并发编程之线程池ThreadPoolExecutor分析下

发布时间:2024年01月18日

目录

execute方法

addWorker方法

runWorker方法

processWorkerExit方法

getTask方法

shutdown方法

shutdownNow方法

?mainLock锁


execute方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // 获取ctl
        // ctl初始值是ctlOf(RUNNING, 0),表示线程池处于运行中,工作线程数为0
        int c = ctl.get();
        // 工作线程数小于corePoolSize,则添加工作线程,并把command作为该线程要执行的任务
        if (workerCountOf(c) < corePoolSize) {
        // true表示添加的是核心工作线程,具体一点就是,在addWorker内部会判断当前工作线程数是不是超过了corePoolSize
        // 如果超过了则会添加失败,addWorker返回false,表示不能直接开启新的线程来执行任务,而是应该先入队
            if (addWorker(command, true))
                return;
        // 如果添加核心工作线程失败,那就重新获取ctl,可能是线程池状态被其他线程修改了
        // 也可能是其他线程也在向线程池提交任务,导致核心工作线程已经超过了corePoolSize
            c = ctl.get();
        }
        // 线程池状态是否还是RUNNING,如果是就把任务添加到阻塞队列中
        if (isRunning(c) && workQueue.offer(command)) {
        // 在任务入队时,线程池的状态可能也会发生改变
        // 再次检查线程池的状态,如果线程池不是RUNNING了,那就不能再接受任务了,就得把任务从队列中移除,并进行拒绝策略
        // 如果线程池的状态没有发生改变,仍然是RUNNING,那就不需要把任务从队列中移除掉
        // 不过,为了确保刚刚入队的任务有线程会去处理它,需要判断一下工作线程数,如果为0,那就添加一个非核心的工作线程
        // 添加的这个线程没有自己的任务,目的就是从队列中获取任务来执行
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 如果线程池状态不是RUNNING,或者线程池状态是RUNNING但是队列满了,则去添加一个非核心工作线程
        // 实际上,addWorker中会判断线程池状态如果不是RUNNING,是不会添加工作线程的
        // false表示非核心工作线程,作用是,在addWorker内部会判断当前工作线程数已经超过了
maximumPoolSize,如果超过了则会添加不成功,执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

addWorker方法

? ? ? ?addWorker方法是核心方法,是用来添加线程的,core参数表示添加的是核心线程还是非核心线程。
1. 如果是要添加核心工作线程,那么就得判断目前的工作线程数是否超过corePoolSize
? ? ? ? 如果没有超过,则直接开启新的工作线程执行任务。
? ? ? ? 如果超过了,则不会开启新的工作线程,而是把任务进行入队。

2. 如果要添加的是非核心工作线程,那就要判断目前的工作线程数是否超过maximumPoolSize
?? ? ? 如果没有超过,则直接开启新的工作线程执行任务。
? ? ? ?如果超过了,则拒绝执行任务。

? ? ? ?在addWorker方法中,首先就要判断工作线程有没有超过限制,如果没有超过限制再去开启一个线程。还要判断线程池的状态,如果线程池的状态不是RUNNING状态了,那就没必要要去添加线程了,当然有一种特例,就是线程池的状态是SHUTDOWN,但是队列中有任务, 那此时还是需要添加添加一个线程的。
特例:
???????在线程池中有这么一个参数:allowCoreThreadTimeOut,表示是否允许核心工作线程超时,意思就是是否允许核心工作线程回收,默认这个参数为false,但是我们可以调用 allowCoreThreadTimeOut(boolean value)来把这个参数改为true,只要改了,那么核心工作线程也 就会被回收了,那这样线程池中的所有工作线程都可能被回收掉,那如果所有工作线程都被回收掉之后,阻塞队列中来了一个任务,此时就需要再添加一个线程,这样就形成了特例情况。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            //表示当前线程池的控制状态。用32bit位存储的多种信息
            int c = ctl.get();
            //表示线程池的当前状态
            int rs = runStateOf(c);

            // 线程池如果是SHUTDOWN状态并且队列非空则创建线程,如果队列为空则不创建线程了
            // 线程池如果是STOP状态则直接不创建线程了
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            // 判断工作线程数是否超过了限制
            // 如果超过限制了,则return false
            // 如果没有超过限制,则修改ctl,增加工作线程数,cas成功则退出外层retry循环,去创建新的工作线程
            // 如果cas失败,则表示有其他线程也在提交任务,也在增加工作线程数,此时重新获取ctl
            // 如果发现线程池的状态发生了变化,则继续回到retry,重新判断线程池的状态是不是SHUTDOWN或STOP
            // 如果状态没有变化,则继续利用cas来增加工作线程数,直到cas成功
            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;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        // ctl修改成功,也就是工作线程数+1成功
        // 接下来就要开启一个新的工作线程了
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // Worker实现了Runnable接口
            // 在构造一个Worker对象时,就会利用ThreadFactory新建一个线程
            // Worker对象有两个属性:
            // Runnable firstTask:表示Worker待执行的第一个任务,第二个任务会从阻塞队列中获取
            // Thread thread:表示Worker对应的线程,就是这个线程来获取队列中的任务并执行的
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                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());

                    // 如果线程池的状态是RUNNING
                    // 或者线程池的状态变成了SHUTDOWN,但是当前线程没有自己的第一个任务,那就表示当前调用addWorker方法是为了从队列中获取任务来执行
                    // 正常情况下线程池的状态如果是SHUTDOWN,是不能创建新的工作线程的,但是队列中如果有任务,那就是上面说的特例情况
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {

                        // 如果Worker对象对应的线程已经在运行了,那就有问题,直接抛异常
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        // workers用来记录当前线程池中工作线程,调用线程池的shutdown方法时会遍历worker对象中断对应线程
                        workers.add(w);
                        // largestPoolSize用来跟踪线程池在运行过程中工作线程数的峰值
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 运行线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            // 在上述过程中如果抛了异常,需要从works中移除所添加的work,并且还要修改ctl,工作线程数-1,表示新建工作线程失败
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 最后表示添加工作线程成功
        return workerStarted;
    }

addWorker方法,核心逻辑如下:
? ? ? ? 先判断工作线程数是否超过了限制,修改ctl,使得工作线程数+1,构造Work对象,并把它添加到workers集合中,启动Work对象对应的工作线程。

runWorker方法

Work的构造方法

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

? ? ? ? 通过ThreadPoolExecutor的execute方法提交一个任务时,内部会调用addWorker方法。addWorker方法用于添加新的工作线程。在这个方法中,会创建一个新的Worker对象,并调用其runWorker方法。Worker类是ThreadPoolExecutor中的内部类,每个工作线程都对应一个Worker对象。在addWorker方法中创建了Worker对象后,会启动对应的线程,线程启动后会调用runWorker方法。

final void runWorker(Worker w) {
    // 就是当前工作线程
    Thread wt = Thread.currentThread();
    
    // 把Worker要执行的第一个任务拿出来
    Runnable task = w.firstTask;
    w.firstTask = null;
    
    // 把state从‐1改为0 意思是可以允许中断
    w.unlock(); // allow interrupts
    
    boolean completedAbruptly = true;
    try {
        
        //task不为空 或者阻塞队列中拿到了任务
        while (task != null || (task = getTask()) != null) {
            // 只要拿到了任务,就要去执行任务
            
            // 锁定工作线程,确保在执行任务时不会被其他线程干扰。
            w.lock();
            
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            
           
            // 如果当前线程池状态等于stop 就中断。Thread.interrupted() 中断标志
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            
            
            try {
                // 空方法,给自定义线程池来实现
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 执行任务
                    // 注意执行任务时可能会抛异常,如果抛了异常会先依次执行finally,从而导致completedAbruptly = false这行代码没有执行
                    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++; // 跟踪当前Work总共执行了多少了任务
                w.unlock();
            }
        }
        
        // 正常退出了While循环,completedAbruptly=false,表示线程正常退出
        // 如果是执行任务的时候抛了异常,虽然也退出了循环,但是是不会执行这行代码的,只会直接进去下面的finally块中
        // 所以,要么是线程从队列中获取任务时阻塞超时了从而退出了循环会进入到这里
        // 要么是线程在阻塞的过程中被中断了,在getTask()方法中会处理中断的情况,如果被中断了,那么getTask()方法会返回null,从而退出循环
        completedAbruptly = false;
    } finally {
        // 善后工作处理,比如修改ctl,工作线程数-1
        processWorkerExit(w, completedAbruptly);
    }
}

processWorkerExit方法

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    // 如果completedAbruptly为true,表示是执行任务的时候抛了异常,那就修改ctl,工作线程数-1
    // 如果completedAbruptly为false,表示是线程阻塞超时了或者被中断了,实际上也要修改ctl,工作线程数-1
    // 只不过在getTask方法中已经做过了,这里就不用再做一次了
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 当前Work要运行结束了,将完成的任务数累加到线程池上
        completedTaskCount += w.completedTasks;
        
        // 将当前Work对象从workers中移除
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    
    // 因为当前是处理线程退出流程中,所以要尝试去修改线程池的状态为TINDYING
    tryTerminate();
    
    
    int c = ctl.get();
    // 如果线程池的状态为RUNNING或者SHUTDOWN,则可能要替补一个线程
    if (runStateLessThan(c, STOP)) {
        
        // completedAbruptly为false,表示线程是正常要退出了,则看是否需要保留线程
        if (!completedAbruptly) {
            
            // 如果allowCoreThreadTimeOut为true,但是阻塞队列中还有任务,那就至少得保留一个工作线程来处理阻塞队列中的任务
            // 如果allowCoreThreadTimeOut为false,那min就是corePoolSize,表示至少得保留corePoolSize个工作线程活着
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            
            // 如果当前工作线程数大于等于min,则表示符合所需要保留的最小线程数,那就直接return,不会调用下面的addWorker方法新开一个工作线程了
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        
        // 如果线程池的状态为RUNNING或者SHUTDOWN
        // 如果completedAbruptly为true,表示当前线程是执行任务时抛了异常,那就得新开一个工作线程
        // 如果completedAbruptly为false,但是不符合所需要保留的最小线程数,那也得新开一个工作线程
        addWorker(null, false);
    }
}

???????某个工作线程正常情况下会不停的循环从阻塞队列中获取任务来执行,正常情况下就是通过阻塞来保证线程永远活着,但是会有一些特殊情况:
1. 如果线程被中断了,那就会退出循环,然后做一些善后处理,比如ctl中的工作线程数-1,然后自己运行结束
2. 如果线程阻塞超时了,那也会退出循环,此时就需要判断线程池中的当前工作线程够不够,比如是否有
corePoolSize个工作线程,如果不够就需要新开一个线程,然后当前线程自己运行结束,这种看上去效率比较低,但是也没办法,当然如果当前工作线程数足够,那就正常,自己正常的运行结束即可
3. 如果线程是在执行任务的时候抛了移除,从而退出循环,那就直接新开一个线程作为替补,当然前提是线程池的状态是RUNNING

getTask方法

private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);//获取线程池运行状态

            // 如果线程池状态是STOP,表示当前线程不需要处理任务了,那就修改ctl工作线程数-1
            // 如果线程池状态是SHUTDOWN,但是阻塞队列中为空,表示当前任务没有任务要处理了,那就修改ctl工作线程数-1
            // return null表示当前线程无需处理任务,线程退出
            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            // 当前工作线程数
            int wc = workerCountOf(c);

            // 用来判断当前线程是无限阻塞还是超时阻塞,如果一个线程超时阻塞,那么一旦超时了,那么这个线程最终就会退出
            // 如果是无限阻塞,那除非被中断了,不然这个线程就一直等着获取队列中的任务
            // allowCoreThreadTimeOut为true,表示线程池中的所有线程都可以被回收掉,则当前线程应该直接使用超时阻塞,一旦超时就回收
            // allowCoreThreadTimeOut为false,则要看当前工作线程数是否超过了corePoolSize,如果超过了,则表示超过部分的线程要用超时阻塞,一旦超时就回收
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            // 如果工作线程数超过了工作线程的最大限制或者线程超时了,则要修改ctl,工作线程数减1,并且return null
            // return null就会导致外层的while循环退出,从而导致线程直接运行结束
            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;
                // 超时了,重新进入循环,上面的代码会判断出来当前线程阻塞超时了,最后return null,线程会运行结束
                timedOut = true;
            } catch (InterruptedException retry) {
                // 从阻塞队列获取任务时,被中断了,也会再次进入循环,此时并不是超时,但是重新进入循环后,会判断线程池的状态
                // 如果线程池的状态变成了STOP或者SHUTDOWN,最终也会return null,线程会运行结束
                // 但是如果线程池的状态仍然是RUNNING,那当前线程会继续从队列中去获取任务,表示忽略了本次中断
                // 只有通过调用线程池的shutdown方法或shutdownNow方法才能真正中断线程池中的线程
                timedOut = false;
            }
        }
    }

注意:只有通过调用线程池的shutdown方法或shutdownNow方法才能真正中断线程池中的线程。

? ? ? ? 在java,中断一个线程,只是修改了该线程的一个标记,并不是直接kill了这个线程,被中断的线程到底要不要消失,由被中断的线程自己来判断。

shutdown方法

???????线程池的shutdown方法,表示要关闭线程池,不接受新任务,但是要把阻塞队列中剩余的任务执行完。
???????根据前面execute方法的源码,只要线程池的状态不是RUNNING,那么就表示线程池不接受新任务,所以shutdown方法要做的第一件事情就是修改线程池状态。
???????第二件事情就是要中断线程池中的工作线程,这些工作线程要么在执行任务,要么在阻塞等待任务:
1. 对于在阻塞等待任务的线程,直接中断即可,
2. 对于正在执行任务的线程,其实只要等它们把任务执行完,就可以中断了,因为此时线程池不能接受新任务,所以正在执行的任务就是最后剩余的任务

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改ctl,将线程池状态改为SHUTDOWN
        advanceRunState(SHUTDOWN);
        // 中断工作线程
        interruptIdleWorkers();
        // 空方法,给子类扩展使用
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    // 调用terminated方法
    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在执行任务时会先加锁,执行完任务之后会释放锁
            // 所以只要这里拿到了锁,就表示线程空出来了,可以中断了
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

shutdownNow方法

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        // 修改ctl,将线程池状态改为STOP
        advanceRunState(STOP);
        // 中断工作线程
        interruptWorkers();
        // 返回阻塞队列中剩余的任务
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    
    // 调用terminated方法
    tryTerminate();
    return tasks;
}
private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        // 中断所有工作线程,不管有没有在执行任务
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}


void interruptIfStarted() {
    Thread t;
    
    // 只要线程没有被中断,那就中断线程,中断的线程虽然也会进入processWorkerExit方法,但是该方法中判断了线程池的状态
    // 线程池状态为STOP的情况下,不会再开启新的工作线程了
    // 这里getState>-0表示,一个工作线程在创建好,但是还没运行时,这时state为-1,可以看看Worker的构造方法就知道了
    // 表示一个工作线程还没开始运行,不能被中断,就算中断也没意义,都还没运行
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
        try {
            t.interrupt();
        } catch (SecurityException ignore) {
        }
    }
}

?mainLock锁

? ? ? ? mainLock是一个重要的锁,它的作用是确保对线程池关键操作的互斥访问。mainLock是ReentrantLock的实例,它提供了可重入的互斥锁,用于保护对线程池状态的修改。
mainLock在ThreadPoolExecutor中的主要作用:
? 1. 保护线程池状态的修改: ThreadPoolExecutor内部有一些关键的状态变量,如线程池状态runState、工作线程数workerCount等。这些状态在不同的操作中需要进行修改,而mainLock确保了这些状态变量的修改是线程安全的。例如,当线程池执行execute方法添加新任务时,会先获取mainLock,以确保在修改线程池状态时不会被其他线程干扰。
? 2. 协调线程池的启动和关闭: mainLock在线程池启动、关闭等关键操作中发挥了重要作用。通过获取mainLock,线程池可以保证这些操作的原子性和互斥性,防止在多线程环境下出现竞态条件。
? 3. 保护工作线程的添加和移除: 线程池会根据需要动态地添加或移除工作线程。mainLock用于保护这些操作,以防止在修改工作线程数时出现并发问题。
? 4. 确保对任务队列的安全访问: 线程池的任务队列是一个关键的数据结构,mainLock也用于确保对任务队列的安全访问。在向任务队列添加或移除任务时,需要获取mainLock以保证线程安全性。

文章来源:https://blog.csdn.net/m0_61853556/article/details/135661518
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。