线程池原理源码分析

线程池原理分析

线程池好处

  • 创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程

  • 控制并发的数量,并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。(主要原因)

  • 可以对线程做统一管理

线程池原理

线程池结构

image-20210410124215100

从图中可以看出,ThreadPoolExecutor 继承了 AbstractExecutorService 类, AbstractExecutorService 实现了 ExecutorService 接口,ExecutorService 接口继承了 Executor 接口。具体每个接口和类的方法可以查看下图

  • Executor 接口

       image-20210410124815946

  • ExecutorService 接口

    image-20210410124724959

  • AbstractExecutorService

     image-20210410131133717

上面两个接口,定义了很多的接口方法,包括 execute执行的核心方法,还有一些设置线程状态等方法

// 关闭线程池,已提交的任务继续执行,不接受继续提交新任务
    void shutdown();
​
    // 关闭线程池,尝试停止正在执行的所有任务,不接受继续提交新任务
    // 它和前面的方法相比,加了一个单词“now”,区别在于它会去停止当前正在进行的任务
    List<Runnable> shutdownNow();
​
    // 线程池是否已关闭
    boolean isShutdown();
​
    // 如果调用了 shutdown() 或 shutdownNow() 方法后,所有任务结束了,那么返回true
    // 这个方法必须在调用shutdown或shutdownNow方法之后调用才会返回true
    boolean isTerminated();
​
    // 等待所有任务完成,并设置超时时间
    // 我们这么理解,实际应用中是,先调用 shutdown 或 shutdownNow,
    // 然后再调这个方法等待所有的线程真正地完成,返回值意味着有没有超时
    boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;
​
    // 提交一个 Callable 任务
    <T> Future<T> submit(Callable<T> task);
​
    // 提交一个 Runnable 任务,第二个参数将会放到 Future 中,作为返回值,
    // 因为 Runnable 的 run 方法本身并不返回任何东西
    <T> Future<T> submit(Runnable task, T result);
​
    // 提交一个 Runnable 任务
    Future<?> submit(Runnable task);
​
    // 执行所有任务,返回 Future 类型的一个 list
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
​
    // 也是执行所有任务,但是这里设置了超时时间
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;
​
    // 只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException;
    //同上一个方法,只有其中的一个任务结束了,就可以返回,返回执行完的那个任务的结果,
    // 不过这个带超时,超过指定的时间,抛出 TimeoutException 异常
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;

FutureTask类是Runable的实现类,FutureTask<V> implements RunnableFuture<V>,interface RunnableFuture<V> extends Runnable, Future<V>,然后每个Runnable通常包装成FutureTask,然后调用executor.execute(Runnable command)将其提交给线程池。Runnable 的 void run() 方法是没有返回值的,所以,通常,如果我们需要的话,会在 submit 中指定第二个参数作为返回值。

  • ThreadPoolExecutor

image-20210410125339322image-20210410125233158

核心方法

  1. 构造方法

execute(Runnable runnable)方法,是交给具体的执行器去实现,ThreadPoolExecutor 该类就是具体的执行器,其中 ThreadPoolExecutor 的核心方法就是构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        // 这几个参数都是必须要有的
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
​
​
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
 }
  • corePoolSize:核心线程数,核心线程数默认情况下会一直存在与线程池中,即使这个核心线程是闲置的,而非核心的线程如果长时间闲置,就会被销毁。

  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数。

  • workQueue:任务队列,BlockingQueue接口的某个实现类。

  • keepAliveTime:非核心线程闲置的活跃时间,非核心线程的如果处于闲置状态的时间超过该值,则会被销毁。如果设置了 allowCoreThreadTimeOut(true),则会作用与核心线程。

  • unit:keepAliveTime 的时间单位。

  • threadFactory:用于生成线程的工厂。

  • handler:当线程池已经满了,但是又有新的任务提交的时候,该采取什么策略由这个来指定。有几种方式可供选择,像抛出异常、直接拒绝然后返回等,也可以自己实现相应的接口实现自己的逻辑。

    • ThreadPoolExecutor.AbortPolicy: 默认的拒绝策略,丢弃任务并抛出RejectedExecutionException异常。

      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                  throw new RejectedExecutionException("Task " + r.toString() +
                  " rejected from " +
                  e.toString());
      }
      
    • ThreadPoolExecutor.DiscardOldestPolicy: 如果线程池没有关闭,则丢弃最久没被处理的任务。然后执行当前任务。

      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           if (!e.isShutdown()) {
              e.getQueue().poll();
              e.execute(r);
           }
      }
    • ThreadPoolExecutor.DiscardPolicy:丢弃当前任务,不做任何操作。

      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
      }
    • ThreadPoolExecutor.CallerRunsPolicy:如果线程池没有关闭,则由调用线程处理该任务。

      public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
          if (!e.isShutdown()) {
              r.run();
          }
      }
  1. execute(Runnable command)

    • ctl.get(): 32位,“线程池状态” 和 “线程数” 的整数,状态是前三位,线程数是后29位

    • workerCount:正在工作的线程数量

    • runState:线程池运行状态

    public void execute(Runnable command) {
            if (command == null)
                throw new NullPointerException();
            /*
             * Proceed in 3 steps:
             * 1. 当前正在运行的线程数量少于核心数,尝试调用 addWorker 开启一个新线程执行任务。
             * 调用 addWorker 会再次检查 runState、workCount 判断新建线程的情况,返回false
             * 表示新建线程失败, 返回 true,流程结束。
             *
             * 2. 如果任务能够成功入队,再次 double-check线程状态、线程数量,判断是否应该添加线程。
             * 因为自从上次check后,可能存在线程已经执行完毕,或者线程池已经shut down,如果存在上
             * 述的问题,那么就会回滚执行出队操作。
             *
             * 3. 如果不能入队成功,则会调用addWorker开启一个线程执行。如果开启失败,则执行拒绝策略。
             */
            int c = ctl.get();
            // 当前线程数少于核心线程数,那么直接调用addWorker添加一个worker执行任务。
            // true 为核心线程,false为非核心线程。线程数在 [0, corePoolSize)直接开启新的线程
            if (workerCountOf(c) < corePoolSize) {
                // 执行成功后则直接return
                if (addWorker(command, true))
                    return;
                c = ctl.get();
            }
            // 两种情况会走到这里:
            // 1. 当前线程数 >= 核心线程数。
            // 2. 或者上一步调用addWorker新建核心线程失败。
            if (isRunning(c) && workQueue.offer(command)) {
                // 线程池状态为正在运行并且将任务加入阻塞队列成功
                // 再次检查线程池状态、数量,
                int recheck = ctl.get();
                // 如果线程池没有运行,则移除刚刚入队的任务。如果移除成功,则执行拒绝策略
                if (! isRunning(recheck) && remove(command))
                    reject(command);
                // 走到这里前提:线程正在运行或者移除任务失败
                // 因为出队失败,那么刚刚添加的任务还在阻塞队列中,如果线程数为0,
                //新建非核心线程执行阻塞队列中的任务。意图是:担心任务提交到队列中了,但是线程都关闭了
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
            // 走到这里:线程池没有运行或者任务入队失败(队列满了)
            // 直接调用addWorker新建非核心线程执行任务
            else if (!addWorker(command, false))
                // 线程数 >= maximumPoolSize,addWorker失败,执行拒绝策略
                reject(command);
    }

    为什么二次检查double-check?

    因为如果任务已经加入阻塞队列,但是万一线程池没有处于运行状态,那么刚入队的任务永远不会执行。

    总结流程:

    1. 线程数量 < corePoolSize,无论线程是否空闲,都会新建一个核心线程执行任务。

    2. 线程数量 >= corePoolSize ,任务会进入工作队列中,然后空闲的核心线程会依次去缓存队列中取任务执行(体现了线程复用)。

    3. 当工作队列满了,新建非核心线程执行当前任务。

    4. 工作队列满了、线程数 >= maximumPoolSize,执行拒绝策略。

  2. addWorker(Runnable firstTask, boolean core):真正创建工作线程的方法

    • firstTask:当前任务。可以为null,表示执行工作队列中的任务

    • core:是否新建核心线程,true:使用核心线程数 corePoolSize 作为创建线程的界限,false:使用最大线程数 maximumPoolSize 作为界限

    private boolean addWorker(Runnable firstTask, boolean core) {
            retry:
            for (;;) {
                int c = ctl.get();
                // 获取线程运行状态
                int rs = runStateOf(c);
                // 线程池状态大于 SHUTDOWN,其实也就是 STOP, TIDYING 或 TERMINATED
                // 当状态大于 SHUTDOWN 时,不允许提交任务,且中断正在执行的任务
                // 满足 rs > SHUTDOWN、firstTask 不会空,工作队列不为空时,直接返回false
                if (rs >= SHUTDOWN &&
                    ! (rs == SHUTDOWN &&
                       firstTask == null &&
                       ! workQueue.isEmpty()))
                    return false;
                // 
                for (;;) {
                    // 获得工作线程数量
                    int wc = workerCountOf(c);
                    // core为true时,wc >= corePoolSize
                    // 或者core为false时,wc >= maximumPoolSize 直接返回false
                    if (wc >= CAPACITY ||
                        wc >= (core ? corePoolSize : maximumPoolSize))
                        return false;
                     // CAS 增加工作线程数 + 1,成功则中断for循环
                    if (compareAndIncrementWorkerCount(c))
                        break retry;
                    c = ctl.get();  // Re-read ctl
                    // CAS 失败,判断重新读取的线程状态,如果与初始的rs状态不一致,
                    // 那么回到for里层再次执行
                    if (runStateOf(c) != rs)
                        continue retry;
                    // else CAS failed due to workerCount change; retry inner loop
                }
            }
    ​
            // 走到这里说明线程池的状态是正确的或者工作线程已经自增1
            // worker 是否已经启动
            boolean workerStarted = false;
            // 是否已将这个 worker 添加到 workers 这个 HashSet 中
            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 {
                        // Recheck while holding lock.
                        // Back out on ThreadFactory failure or if
                        // shut down before lock acquired.
                        int rs = runStateOf(ctl.get());
                       // 小于 SHUTTDOWN 那就是 RUNNING,这个自不必说,是最正常的情况
                       // 如果等于 SHUTDOWN,前面说了,不接受新的任务,但是会继续执行等待队列中的任务
                        if (rs < SHUTDOWN ||
                            (rs == SHUTDOWN && firstTask == null)) {
                            // 刚刚新建的线程状态还未启动,alive表示线程启动但是还未死亡
                            if (t.isAlive()) // precheck that t is startable
                                throw new IllegalThreadStateException();
                            // 将当前worker添加进线程中的workers,存放的都是工作线程
                            workers.add(w);
                            int s = workers.size();
                            // 记录线程池曾经达到的最大线程数量值
                            if (s > largestPoolSize)
                                largestPoolSize = s;
                            // 设置worker状态为已添加
                            workerAdded = true;
                        }
                    } finally {
                        mainLock.unlock();
                    }
                    // 添加成功则启动线程
                    if (workerAdded) {
                        // 这个方法很重要,直接工作方法,这里会触发Worker类的run方法被JVM调用,
                        // run方法中又会调用runWorker方法
                        t.start();
                        workerStarted = true;
                    }
                }
            } finally {
                // 刚才说了进入到这个try代码块时,已经将workercount工作数量加1
                // 这里时如果工作线程没有开启,那么会回滚。将wc - 1
                if (! workerStarted)
                    addWorkerFailed(w);
            }
            return workerStarted;
    }
  3. Woker.runWorker(Worker w): 工作线程执行任务的具体方法

    • w:需要执行的工作者线程

    final void runWorker(Worker w) {
            Thread wt = Thread.currentThread();
            // 获取当前工作线程需要执行的任务
            Runnable task = w.firstTask;
            w.firstTask = null;
            // 释放锁
            w.unlock(); // allow interrupts
            boolean completedAbruptly = true;
            try {
            	// 如果task为空的话,则会循环去调用 getTask方法获取任务
            	// getTask则是从工作队列中获取的头部任务
                while (task != null || (task = getTask()) != null) {
                    w.lock();
                    // 如果线程池状态大于等于 STOP,那么意味着该线程也需要中断
                    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;
                        // 完成任务数自增1
                        w.completedTasks++;
                        w.unlock();
                    }
                }
                completedAbruptly = false;
            } finally {
                // 如果到这里,需要执行线程关闭:
                // 1. 说明 getTask 返回 null,也就是说,队列中已经没有任务需要执行了,执行关闭
                // 2. 任务执行过程中发生了异常
                // 第一种情况,已经在代码处理了将 workCount 减 1,这个在 getTask 方法分析中会说
                // 第二种情况,workCount 没有进行处理,所以需要在 processWorkerExit 中处理
                processWorkerExit(w, completedAbruptly);
            }
    }

    线程池怎么复用线程?

    首先执行创建这个 worker 时就有需要执行的当前任务,当执行完当前任务后,worker 的生命周期没有结束,而是在 while 循环中,前提是线程池和当前线程的状态正常,那么worker会不断地调用 getTask 方法从工作队列中获取任务然后调用 task.run() 执行任务,从而达到线程复用的目的。只有 getTask 方法不返回 null,此线程则不会退出关闭。

  4. 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.
                // 检查rs状态如果线程池状态为不可用,工作队列是空的,
                // 将工作线程数 - 1,最后直接返回null
                if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                    decrementWorkerCount();
                    return null;
                }
    ​
                int wc = workerCountOf(c);
    ​
                // Are workers subject to culling?
                // 设置核心线程空闲时是否会被销毁,默认为false,如果为true则会被销毁
                boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
                // 1. 如果wc>最大线程数并且工作队列时空的,递减wc
                // 2. 如果设置 allowCoreThreadTimeOut为true 或者wc > corePoolSize && wc <=                 maximumPoolSize 
                // 并且在规定时间没有获取到任务。则会递减wc
                if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {
                    if (compareAndDecrementWorkerCount(c))
                        return null;
                    continue;
                }
    ​
                try {
                    // timed为true,则设置超时时间获取任务,否则使用take,一直阻塞,直到
                    //队列中有任务加入时,线程才会被唤醒。这里就是保证核心线程不被回收的关键
                    Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take();
                    if (r != null)
                        return r;
                    timedOut = true;
                } catch (InterruptedException retry) {
                    timedOut = false;
                }
            }
    }

    核心线程会一直卡在 workQueue.take 方法,被阻塞挂起,不会占用CPU资源,直到获取到Runnable任务然后返回。如果 allowCoreThreadTimeOut 设置为true,那么所有的工作线程都会去使用 poll 调用,当返回null时,线程会被销毁。

    非核心线程调用 poll 超时未获取到时,timedOut = true, 下一次while循环判断时,timed = wc >corePoolSize = true,然后调用 compareAndDecrementWorkerCount 后,直接返回 null。Worker对象的 run() 方法循环体的判断为 null,任务结束,线程被回收。

猜你喜欢

转载自blog.csdn.net/LarrYFinal/article/details/115596931