Java线程池 - 重点总结

本文深入探讨Java线程池的核心概念,包括线程池的层级关系、核心成员变量、执行流程、服务策略和阻塞队列。重点关注ThreadPoolExecutor的实现细节,如execute方法、Worker内部类以及线程池的四种常见实现。通过对源码的分析,揭示了线程池如何处理任务调度、线程管理和异常情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文参考:

https://www.cnblogs.com/exe19/p/5359885.html   写的很好,但是jdk版本老,与8的代码不符。

https://www.jianshu.com/p/50fffbf21b39  可以简单看一下

https://www.cnblogs.com/trust-freedom/p/6681948.html   jdk版本与8代码相符,流程较为详细

 



Ch1.基础 必会

线程池的核心类与层级关系:

图中没有画出来但是也很常用的类:Executors。是线程池的静态工厂,提供了快捷创建线程池的静态方法。

ThreadPoolExecutor的核心成员变量:

  • corePoolSize和maximumPoolSize:分别是核心线程池的容量和整个线程池的最大容量。核心线程与非核心线程的区别是,如果没有调用allowCoreThreadTimeOut(true),那么核心线程不会因为一直没有被分配任务而被销毁,而非核心线程当处于空闲状态(即没有执行任何任务)一定时间后(keepAliveTime),会被销毁。
  • keepAliveTime和unit:表示线程没有任务执行时最多保持多久时间会终止。如果调用了allowCoreThreadTimeOut(true),keepAliveTime将会作用于所有线程上,否则只作用于非核心线程上。  unit是keepAliveTime的时间单位,在TimeUnit枚举类型中有DAYS,HOURS,MINUTES,SECONDS,MILLISECONDS,MICROSECONDS,NANOSECONDS等7中静态属性对应7个取值。二者共同决定了线程的空闲终止时间。
  • workQueue和threadFactory:workQueue的类型为BlockingQueue<Runnable>即线程的阻塞队列,队列中的线程是等待被执行的线程。当所有的核心线程都在执行任务时,新添加的任务会被添加到workQueue中等待处。如果队列满了,则新建非核心线程执行任务。   threadFactory线程工厂,用于创建线程。
  • handler,用于处理拒绝任务时的策略,有几种情况:ThreadPoolExecutor.AbortPolicy(丢弃任务并抛出RejectdExecutionExeption异常);DiscardPolicy(丢弃任务但是不抛出异常);DiscardOldestPolicy(丢弃队列最前面的任务,然后重新尝试执行任务);CallerRunsPolicy(由调用线程处理该任务)
  • private final AtomicInteger ctl : 老版本的jdk使用volatile的变量workerCount与runState,分别表示线程数量和线程池的运行状态。jdk8中使用一个变量ctl来表示这两个值。其高3位用于维护线程池的运行状态,低29位维护线程池中的线程数量。这样就需要维护两个volatile来保证线程之间的可见性了。
  • mainLock = new ReentrantLock() : 线程池的主要状态锁,对线程池的状态(如线程池大小、runState/ctl)进行改变要获取这个锁。注意这个锁是个可重入锁。
  • HashSet<Worker> workers:其中Worker是内部类,实现了Runnable接口。该set包含了线程池中的所有worker线程。worker其实就是一个Runnable,其也是需要构造成一个Thread对象,然后调用Thread start方法运行的。只不过在worker的run方法中是定一个了一个runWorker的方法。这个方法的主要内容从 for 循环的不停的从task队列中获取对应的runnable的task,然后同步调用这个task的run()方法。其实就是在某个线程中,不停的拿队列中的任务进行执行。

线程池的执行流程

当试图通过execute方法将一个Runnable任务添加到线程池中时,按照如下顺序来处理:

  1. 如果线程池中的线程数量少于corePoolSize,就创建新的线程来执行新添加的任务
  2. 如果线程池中的线程数量大于等于corePoolSize,但队列workQueue未满,则将新添加的任务放到workQueue中,按照FIFO的原则依次等待执行(线程池中有线程空闲出来后依次将队列中的任务交付给空闲的线程执行)
  3. 如果线程池中的线程数量大于等于corePoolSize,且队列workQueue已满,但线程池中的线程数量小于maximumPoolSize,则会创建新的线程来处理被添加的任务
  4. 如果线程池中的线程数量等于了maximumPoolSize,就用RejectedExecutionHandler来做拒绝处理

服务策略与阻塞队列

线程池的服务、排队策略,与其成员变量workQueue直接相关,而BlockingQueue的不同实现方式可以产生不同的效果,从而导致采用不同类型的阻塞队列可以产生不同的排队策略。

  • LinkedBlockingQueue(基于链表的无界队列,FIFO),理论上该队列可以对无限多的任务进行排队,将导致在所有corePoolSize线程都工作的情况下将新任务加入到队列中,因此maximumPoolSize的值也就无效了。 如Executors.newFixedThreadPool()是基于链表的无界队列
  • SynchronousQueue(不排队,直接提交),将任务直接交给线程而不保持它们。试图将任务加入阻塞队列的操作会失败,因此需要直接构造一个新的线程来处理新添加的任务并将其加入线程池中,如Executors.newCachedThreadPool()就是这个策略。
  • ArrayBlockingQueue(基于数组结构的有界队列,FIFO),并指定队列的最大长度:使用有界队列可以防止资源耗尽,但也会造成超过队列大小和maximumPoolSize后,提交的任务会被拒绝的问题,比较难调整和控制。

常见线程池实现 

Executors类中有很多用于快捷创建线程池的静态方法,常见的有

  1. Executors.newFixedThreadPool():创建一个指定工作线程数的线程池,其中参数 corePoolSize 和 maximumPoolSize 相等,阻塞队列基于LinkedBlockingQueue.它是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源.
  2. Executors.newCacheThreadPool():可缓存线程池,先查看池中有没有以前建立的线程,如果有,就直接使用。如果没有,就建一个新的线程加入池中,缓存型池子通常用于执行一些生存期很短的异步型任务.
  3. Executors.newScheduledThreadPool(int n):创建一个定长线程池,支持定时及周期性任务执行
  4. Executors.newSingleThreadExecutor():创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
  5. jdk8新加入的Executors.newWorkStealingPool(): 首先WorkStealingPool并不是ThreadPoolExecutor的子类,而是ForkJoinPool。ForkJoinPool是jdk7开始提供的线程池,用于支持将一个任务拆分成多个小任务并行计算,再把多个小任务的计算结果合并成为最终的计算结果,即先fork再join。ForkJoinPool的优势在于,可以充分利用多cpu、多核cpu的优势。


Ch2.提高 深究

ThreadPoolExecutor核心方法:  execute(Runnable command)  分为三个步骤 :

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
        execute 过程分为三步:
        第一步:  如果有空闲核心线程,则start一个核心线程
        如果正在运行的线程数少于corePoolSize,尝试start一个新的线程,并将传入的command作为该
        线程的第一个task。对addWorker方法的调用将原子性地检查线程池运行状态和线程数量,从而
        防止错误的警告在不能添加线程的时候添加线程,方式是返回一个false。当然,如果该方法返回
        true就说明成功添加了worker,execute就可以返回了。
        */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();  //重新获取ctl的值,保证获取到最新的值
        }
        /*
        第二步:  将任务添加到阻塞队列workQueue
        首先检查线程池是否处于运行状态。如果是,且workQueue.offer方法成功将command添加到
        阻塞队列中,先进行一次recheck,获取当前ctl。如果此时线程池此时不再处于运行状态,
        则尝试remove(command)将任务从队列中删除,并且reject(command)拒绝任务;如果
        此时线程池仍然处于运行状态,且worker线程数为0,则addWorker添加一个没有初始任务的
        非核心的worker线程。
        */
        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);
        }
        /*
        第三步:  如果核心线程都在工作或者command进入阻塞队列失败(可能队列已满),
        尝试开启一个新的非核心worker线程执行command,如果失败则说明非核心线程数达到上限,
        拒绝command。
        */
        else if (!addWorker(command, false))
            reject(command);
    }

addWorker(Runnable firstTask, boolean core)

execute中涉及到的比较重要的是addWorker(Runnable firstTask, boolean core)方法:

源码注释翻译:该方法检查是否可以根据当前线程池状态和给定的边界(corePoolSIze或者maxPoolSize)添加新的worker线程。如果是,则相应地调整worker的数量,并且如果可能的话,创建并启动一个新的worker,并运行firstTask作为它的第一个任务。如果线程池停止了或者调用了shutdown则会返回false。如果由于threadFactory返回了null或者异常导致了线程的创建失败了,也会返回false。

    private boolean addWorker(Runnable firstTask, boolean core) {
    //外层循环,判断线程池状态    
    retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
        /*
        线程池的runState越小越是运行中状态。
        running=-1,此时线程池接受新的任务并且处理队列中的任务。
        shutdown=0,此时线程池不能接受新任务,但是会处理队列中的任务。
        stop=1,此时线程池不能接受新的任务,也不执行队列中的任务,而且会中断正在执行的任务。
        tidying=2,此时所有任务都终止了,worker的数量为0。向tidyinig状态过度的线程将会运行        
        terminate()方法。
        terminated=3,此时terminate()方法运行完毕。

        下面的if语句含义为,当state已经至少为shutdown状态时,要经历以下三步来判断是否
        添加worker会失败(注意,每一步都包含上一步下来的的隐含条件):
        1. 如果rs > shutdown,则线程池已经终止,返回false
        2. 如果firstTask不为null,则说明调用者想要在rs已经为shutdown的时候添加新任务,拒绝。
        3. 如果workQueue为空,那么说明队列中已经没有任务了,那么就不需要创建一个firstTask为                
        null的线程了,因为已经没有这个必要了。
        
        在经历了这三步之后,如果没有返回,则说明可以创建新的worker线程,继续下面的步骤。
        */
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            //内存循环,负责worker数量+1
            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //操作成功后,使得worker数量+1,跳出retry循环。
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //重新读取ctl,获取当前线程池状态,如果跟上一步获取到的rs不一样,则继续外层循环
                //重新判断线程池状态
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        //worker数量+1成功后的后续操作:new Worker 添加到workers Set集合 并启动worker线程
        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 {
                    // 获取锁之后再次检查, 来排除ThreadFactory失败或者在获得锁之前
                    // 线程池终止的情况。如果rs为运行状态,或者rs为shutdown但是firstTask为
                    // null,则可以继续后续步骤。
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);  //将w添加到set中
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果worker成功被添加, 则启动worker对应的线程t
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        // 返回添加worker的结果
        return workerStarted;
    }

内部类Worker

Worker类主要维护正在运行任务的线程的中断控制状态,以及其他一些次要的bookkeeping(记账?记录?)。Worker类采用取巧的办法,继承了AbstractQueuedSynchronizer来简化获取和释放关于每个任务执行的锁的操作。这可以避免那些意图唤醒一个正在等待任务的线程的中断错误地中断了正在执行任务的线程,也就是说避免中断作用于正在执行任务的线程。我们实现了一个简单的非重入互斥锁而不是使用ReentrantLock,因为我们不希望工作任务在调用setCorePoolSize等池控制方法时能够重新获取锁。 另外,为了在线程实际开始运行任务之前禁止中断,我们将锁状态初始化为负值,并在启动时清除它(在runWorker中)。

也就是说,Worker不仅是Runnable的,还是一把AQS锁。

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    /**
     * This class will never be serialized, but we provide a
     * serialVersionUID to suppress a javac warning.
     */
    private static final long serialVersionUID = 6138294804551838833L;
 
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread; //利用ThreadFactory和 Worker这个Runnable创建的线程对象
     
    /** Initial task to run.  Possibly null. */
    Runnable firstTask;
     
    /** Per-thread task counter */
    volatile long completedTasks;
 
    /**
     * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        //设置AQS的同步状态private volatile int state,是一个计数器,大于0代表锁已经被获取
        setState(-1); // inhibit interrupts until runWorker 
                      // 在调用runWorker()前,禁止interrupt中断,
                      // 在interruptIfStarted()方法中会判断 getState()>=0
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); 
        //根据当前worker创建一个线程对象
        //当前worker本身就是一个runnable任务,也就是不会用参数的firstTask创建线程,而是调用
        //当前worker.run()时调用firstTask.run()
    }
 
    /** Delegates main run loop to outer runWorker  */
    public void run() {
        runWorker(this); //内部类Worker可以调用外部类ThreadPoolExecutor的方法
    }
 
    // Lock methods
    //
    // The value 0 represents the unlocked state. 0代表“没被锁定”状态
    // The value 1 represents the locked state. 1代表“锁定”状态
 
    protected boolean isHeldExclusively() {
        return getState() != 0;
    }
 
    /**
     * 尝试获取锁
     * 重写AQS的tryAcquire(),AQS本来就是让子类来实现的
     */
    protected boolean tryAcquire(int unused) {
        //尝试一次将state从0设置为1,即“锁定”状态,但由于每次都是state 0->1,而不是+1,那么说明不可重入
        //且state==-1时也不会获取到锁
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread()); //设置exclusiveOwnerThread=当前线程
            return true;
        }
        return false;
    }
 
    /**
     * 尝试释放锁
     * 不是state-1,而是置为0
     */
    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(); }
 
    /**
     * 中断(如果运行)
     * shutdownNow时会循环对worker线程执行
     * 且不需要获取worker锁,即使在worker运行时也可以中断
     */
    void interruptIfStarted() {
        Thread t;
        //如果state>=0、t!=null、且t没有被中断
        //new Worker()时state==-1,说明不能中断
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

Worker和Task的区别:虽然都是Runnable,但是Worker才是真正处于线程池中的线程,Task只是作为Worker构造器中的传入参数,Worker在运行中会调用Task的run方法。

ThreadPoolExecutor.runworker(Worker w)  执行任务

1、Worker线程启动后,通过Worker类的run()方法调用runWorker(this)
2、执行任务之前,首先worker.unlock(),将AQS的state置为0,允许中断当前worker线程
3、开始执行firstTask,调用task.run(),在执行任务前会上锁wroker.lock(),在执行完任务后会解锁,为了防止在任务运行时被线程池一些中断操作中断
4、在任务执行前后,可以根据业务场景自定义beforeExecute() 和 afterExecute()方法
5、无论在beforeExecute()、task.run()、afterExecute()发生异常上抛,都会导致worker线程终止,进入processWorkerExit()处理worker退出的流程
6、如正常执行完当前task后,会通过getTask()从阻塞队列中获取新任务,当队列中没有任务,且获取任务超时,那么当前worker也会进入退出流程

ThreadPoolExecutor.getTask() 获取阻塞队列中的任务

执行流程:
1、首先判断是否可以满足从workQueue中获取任务的条件,不满足return null
    A、线程池状态是否满足:
        (a)shutdown状态 + workQueue为空 或 stop状态,都不满足,因为被shutdown后还是要执行workQueue剩余的任务,但workQueue也为空,就可以退出了
        (b)stop状态,shutdownNow()操作会使线程池进入stop,此时不接受新任务,中断正在执行的任务,workQueue中的任务也不执行了,故return null返回
    B、线程数量是否超过maximumPoolSize 或 获取任务是否超时
        (a)线程数量超过maximumPoolSize可能是线程池在运行时被调用了setMaximumPoolSize()被改变了大小,否则已经addWorker()成功不会超过maximumPoolSize
        (b)如果 当前线程数量>corePoolSize,才会检查是否获取任务超时,这也体现了当线程数量达到maximumPoolSize后,如果一直没有新任务,会逐渐终止worker线程直到corePoolSize
2、如果满足获取任务条件,根据是否需要定时获取调用不同方法:
    A、workQueue.poll():如果在keepAliveTime时间内,阻塞队列还是没有任务,返回null
    B、workQueue.take():如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take方法返回任务
3、在阻塞从workQueue中获取任务时,可以被interrupt()中断,代码中捕获了InterruptedException,重置timedOut为初始值false,再次执行第1步中的判断,满足就继续获取任务,不满足return null,会进入worker退出的流程。

 

processWorkerExit(Worker w, boolean completedAbruptly)
参数:
    worker:                      要结束的worker
    completedAbruptly: 是否突然完成(是否因为异常退出)

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    /**
     * 1、worker数量-1
     * 如果是突然终止,说明是task执行时异常情况导致,即run()方法执行时发生了异常,那么正在工作的worker线程数量需要-1
     * 如果不是突然终止,说明是worker线程没有task可执行了,不用-1,因为已经在getTask()方法中-1了
     */
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted 代码和注释正好相反啊
        decrementWorkerCount();
 
    /**
     * 2、从Workers Set中移除worker
     */
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks; //把worker的完成任务数加到线程池的完成任务数
        workers.remove(w); //从HashSet<Worker>中移除
    } finally {
        mainLock.unlock();
    }
 
    /**
     * 3、在对线程池有负效益的操作时,都需要“尝试终止”线程池
     * 主要是判断线程池是否满足终止的状态
     * 如果状态满足,但还有线程池还有线程,尝试对其发出中断响应,使其能进入退出流程
     * 没有线程了,更新状态为tidying->terminated
     */
    tryTerminate();
 
    /**
     * 4、是否需要增加worker线程
     * 线程池状态是running 或 shutdown
     * 如果当前线程是突然终止的,addWorker()
     * 如果当前线程不是突然终止的,但当前线程数量 < 要维护的线程数量,addWorker()
     * 故如果调用线程池shutdown(),直到workQueue为空前,线程池都会维持corePoolSize个线程,然后再逐渐销毁这corePoolSize个线程
     */
    int c = ctl.get();
    //如果状态是running、shutdown,即tryTerminate()没有成功终止线程池,尝试再添加一个worker
    if (runStateLessThan(c, STOP)) {
        //不是突然完成的,即没有task任务可以获取而完成的,计算min,并根据当前worker数量判断是否需要addWorker()
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //allowCoreThreadTimeOut默认为false,即min默认为corePoolSize
             
            //如果min为0,即不需要维持核心线程数量,且workQueue不为空,至少保持一个线程
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
             
            //如果线程数量大于最少数量,直接返回,否则下面至少要addWorker一个
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
         
        //添加一个没有firstTask的worker
        //只要worker是completedAbruptly突然终止的,或者线程数量小于要维护的数量,就新添一个worker线程,即使是shutdown状态
        addWorker(null, false);
    }
}


执行流程:
1、worker数量-1
    A、如果是突然终止,说明是task执行时异常情况导致,即run()方法执行时发生了异常,那么正在工作的worker线程数量需要-1
    B、如果不是突然终止,说明是worker线程没有task可执行了,不用-1,因为已经在getTask()方法中-1了
2、从Workers Set中移除worker,删除时需要上锁mainlock
3、tryTerminate():在对线程池有负效益的操作时,都需要“尝试终止”线程池,大概逻辑:
    判断线程池是否满足终止的状态
    A、如果状态满足,但还有线程池还有线程,尝试对其发出中断响应,使其能进入退出流程
    B、没有线程了,更新状态为tidying->terminated
4、是否需要增加worker线程,如果线程池还没有完全终止,仍需要保持一定数量的线程
    线程池状态是running 或 shutdown
    A、如果当前线程是突然终止的,addWorker()
    B、如果当前线程不是突然终止的,但当前线程数量 < 要维护的线程数量,addWorker()
    故如果调用线程池shutdown(),直到workQueue为空前,线程池都会维持corePoolSize个线程,然后再逐渐销毁这corePoolSize个线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值