【线程】ThreadPool 线程池底层剖析 (二十)

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码


一、架构

1.1 UML 图

在这里插入图片描述

1.2 Executors返回的线程池对象的弊端
  1. FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
  2. CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。

=== 点击查看top目录 ===

二、 ThreadPoolExecutor 剖析

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
2.1 参数说明
参数描述
corePoolSize线程池核心线程数(平时保留的线程数)
maximumPoolSize线程池最大线程数(线程池最多能起多少Worker)(当workQueue都放不下时,启动新线程,最大线程数)
keepAliveTime超出corePoolSize数量的线程的保留时间。
unitkeepAliveTime单位
workQueue阻塞队列,存放来不及执行的线程
threadFactory线程工厂
handler饱和策略
  • 关于 workQueue 阻塞队列:
  1. ArrayBlockingQueue:构造函数一定要传大小
  2. LinkedBlockingQueue:构造函数不传大小会默认为(Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。
  3. SynchronousQueue:同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
  4. PriorityBlockingQueue : 优先队列
  • 关于 handler 饱和策略:
  1. AbortPolicy(默认):直接抛弃
  2. CallerRunsPolicy:用调用者的线程执行任务,直接执行
  3. DiscardOldestPolicy:抛弃队列中最久的任务
  4. DiscardPolicy:抛弃当前任务

在这里插入图片描述

=== 点击查看top目录 ===

2.2 线程池规则
  • 线程池数量无限制:
  1. 如果Worker数量 <= corePoolSize,那么直接启动一个核心线程来执行 Task,不会放入队列中。
  2. 如果 corePoolSize < Worker数量 < maximumPoolSize:
    a.并且任务队列是LinkedBlockingDeque的时候,超过 corePoolSize 的 Task 会放在任务队列中排队。
    b.并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
  3. 如果 maximumPoolSize < Worker数量:
    a.当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的 maximumPoolSize 设置是无效的,他的线程数最多不会超过 corePoolSize。
    b.当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
  • 线程池数量有限制:
  1. 当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。

synchronousQueue永远没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。

=== 点击查看top目录 ===

2.3 线程池处理 UML 图

在这里插入图片描述

2.4 总结 ( 超级重点)
  1. 总共有3个地方可以放 task , core + queue + max
  2. core先执行、core满就去queue排队、queue队伍满就去max执行,max也满了的话,直接执行拒绝策略
  3. core干完就去消费queue
  4. max中的线程,空闲一段时间就结束

=== 点击查看top目录 ===

三、代码 Demo

  • 情况:

/** ===== SynchronousQueue ==== **/
//1. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
//2. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
//3. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。max 满了,抛异常。

/** ===== LinkedBlockingDeque ==== **/
// 1. 每次+3,塞满 core ,在 queue 里面排队, 由于 LinkedBlockingDeque 无限制数量,所以可以无限数量的 task 排队,然后core空闲了就去queue取task
// 2. 每次+3,塞满 core ,在 queue 里面排队,queue满了,直接丢 max 去执行,后期回收 max 的位置
// 3. 每次+3,塞满 core ,在 queue 里面排队, queue 满里,max 也满了 ,抛出异常

package indi.sword.util.concurrent;

import java.util.concurrent.*;

public class _17_01_TestThreadPool {
    public static void main(String[] args) throws Exception {

        /** ===== SynchronousQueue ==== **/
        //1.  每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
//        testQueue(6, 8, new SynchronousQueue<>());

        //2. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。
//        testQueue(3, 6, new SynchronousQueue<>());

        //3. 每次+3,塞满 core ,无法在 queue 排队, 然后 core 也忙,但是可以丢到 max 里面去执行。max 满了,抛异常。
//        testQueue(3, 5, new SynchronousQueue<>());// RejectedExecutionException 会导致整个 executor 停止

        /** ===== LinkedBlockingDeque ==== **/
        // 1. 每次+3,塞满 core ,在 queue 里面排队, 由于 LinkedBlockingDeque 无限制数量,所以可以无限数量的 task 排队,然后core空闲了就去queue取task
//        testQueue(1, 3, new LinkedBlockingDeque<>());

        // 2. 每次+3,塞满 core ,在 queue 里面排队,queue满了,直接丢 max 去执行,后期回收 max 的位置
//        testQueue(1, 5, new LinkedBlockingDeque<>(1));

        // 3. 每次+3,塞满 core ,在 queue 里面排队, queue 满里,max 也满了 ,抛出异常
        testQueue(1, 3, new LinkedBlockingDeque<>(1));
    }

    /**
     * 队列任务数永远是 0
     *
     * @author jeb_lin
     * 5:32 PM 2019/10/24
     */
    public static void testQueue(int corePoolSize,
                                 int maximumPoolSize,
                                 BlockingQueue<Runnable> workQueue) throws Exception {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.SECONDS, workQueue);
        try {
            System.out.println("输入: corePoolSize -> " + corePoolSize + ", maximumPoolSize -> " + maximumPoolSize);
            executor.execute(new MyRunnable(1));
            executor.execute(new MyRunnable(2));
            executor.execute(new MyRunnable(3));
            System.out.println("---先开三个---");
            print(executor);
            executor.execute(new MyRunnable(4));
            executor.execute(new MyRunnable(5));
            executor.execute(new MyRunnable(6));
            System.out.println("---再开三个---");
            print(executor);
            Thread.sleep(8000);
            System.out.println("----8秒之后----");
            print(executor);
        } finally {
            executor.shutdown();

        }
    }

    private static void print(ThreadPoolExecutor executor) {
        System.out.println("核心线程数 -> " + executor.getCorePoolSize()
                + ", 线程池数 -> " + executor.getPoolSize()
                + ", 队列任务数 -> " + executor.getQueue().size()
                + ", queue -> " + executor.getQueue().toString());
    }


    private static class MyRunnable implements Runnable {
        private int index;
        public MyRunnable(int index){
            this.index = index;
        }
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
                System.out.println("thread-" + this.index + " run");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        public String toString(){
            return "thread-" + index;
        }
    }
}

=== 点击查看top目录 ===

四、Executors 的 4 个常见方法底层

4.1 Executors 的四个常用方法
方法描述
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 (线程可复用)
newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
4.2 newCachedThreadPool 底层

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 (线程可复用)

  • java.util.concurrent.Executors#newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  1. corePoolSize 竟然为0 ,也就意味着过期了就全部回收,不剩余
  2. maximumPoolSize 为默认的最大值,也就是支持无限制的线程并发,也就意味着可能发生OOM
  3. keepAliveTime 空闲线程的最大等待时间,60s后没有被复用立马销毁
  4. 使用 SynchronousQueue ,说明 queue 并不放东西
  5. corePoolSize = 0 加上 SynchronousQueue,说明 queue + core 都不放东西,那么也就意味着只有 max 这个地方放东西,时间一到立马回收。

=== 点击查看top目录 ===

4.3 newFixedThreadPool 底层

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  • java.util.concurrent.Executors#newFixedThreadPool(int) 方法
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  1. corePoolSize = n,规定了 Core 池里面最多放 n 个线程
  2. maximumPoolSize = n ,规定了池子中最多就是 n 个线程 = corePoolSize,也就是全部都是核心线程,多了排队。
  3. keepAliveTime = 0,因为 corePoolSize = maximumPoolSize,也就意味着放满 core 满了,不会再放到 max 里面去,那么也意味着没什么好回收的了。
  4. LinkedBlockingQueue 链表内部结构,无定义长度,意味着线程可以不断进来,不断去排队。允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。
  5. === 关于 LinkedBlockingQueue 与 ArrayBlockingQueue的区别 ===

=== 点击查看top目录 ===

4.4 newScheduledThreadPool 底层

创建一个定长线程池,支持定时及周期性任务执行。

  • java.util.concurrent.ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int) 方法
  • java.util.concurrent.Executors#newSingleThreadScheduledExecutor() 方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
   super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
          Executors.defaultThreadFactory(), defaultHandler);
 }
  1. corePoolSize 自定义传入 N ,也就是core池最多n个线程,支持n线程同时并发
  2. maximumPoolSize 为 Integer.MAX_VALUE ,也就是支持无限制的线程并发,有OOM隐患,这个跟 newCachedThreadPool一样。
  3. keepAliveTime = 0,意味着,执行完毕立马回收
  4. 使用 DelayedWorkQueue ,意味着可以执行延迟操作。
4.5 newSingleThreadExecutor 方法
  • java.util.concurrent.Executors#newSingleThreadExecutor() 方法
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  1. corePoolSize = maximumPoolSize = 1,意味着同一时刻只会有一个线程在执行。
  2. LinkedBlockingQueue 支持无限制的线程并发,有OOM隐患,这个跟 newCachedThreadPool一样。

=== 点击查看top目录 ===

五、源码剖析

5.1 execute 方法
  • java.util.concurrent.ThreadPoolExecutor#execute 方法
 /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         * 如果当前的线程数小于核心线程池的大小,根据现有的线程作为第一个Worker运行的线程,
         * 新建一个Worker,addWorker自动的检查当前线程池的状态和Worker的数量,
         * 防止线程池在不能添加线程的状态下添加线程
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *  如果线程入队成功,然后还是要进行double-check的,因为线程池在入队之后状态是可能会发生变化的
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         * 
         * 如果task不能入队(队列满了),这时候尝试增加一个新线程,如果增加失败那么当前的线程池状态变化了或者线程池已经满了
         * 然后拒绝task
         */
        int c = ctl.get();
        //当前的Worker的数量小于核心线程池大小时,新建一个Worker。
        if (workerCountOf(c) < corePoolSize) { 
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))//recheck防止线程池状态的突变,如果突变,那么将reject线程,防止workQueue中增加新线程
                reject(command);
            else if (workerCountOf(recheck) == 0)//上下两个操作都有addWorker的操作,但是如果在workQueue.offer的时候Worker变为0,
                                                //那么将没有Worker执行新的task,所以增加一个Worker.
                addWorker(null, false);
        }
        //如果workQueue满了,那么这时候可能还没到线程池的maxnum,所以尝试增加一个Worker
        else if (!addWorker(command, false))
            reject(command);//如果Worker数量到达上限,那么就拒绝此线程
    }
5.2 addWorker 方法
private boolean addWorker(Runnable firstTask, boolean core) {
    // 自旋,直到操作成功或者返回
    retry:
    for (;;) {
        // 获取当前线程池的状态
        int c = ctl.get();
        // 获取线程池状态中的运行状态字段(WORKER_COUNT_BITS 部分)
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        // 当前线程池状态为 SHUTDOWN 或 STOPPED 时,禁止向线程队列添加任务
        if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
            return false;

        for (;;) {
            // 获取线程池状态中的 workerCount 字段(后面 29 位)
            int wc = workerCountOf(c);
            // 是否超过线程池最大容量
            // core 为 true 表示 corePoolSize 已满,此时如果线程数到达 maximumPoolSize 仍旧在增加,那就不能再创建新的 worker 了
            // core 为 false,表示 maximumPoolSize 已满,此时如果线程数到达 maximumPoolSize 仍旧在增加,那就不能再创建新的 worker 了
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 自增 workerCount 字段,如果成功就跳出当前循环
            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
        }
    }

    // 程序执行到这里,证明新建 Worker 线程应该是可以的,下面开始具体的操作

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        // 初始化 Worker 对象,并且传入 firstTask 作为第一个任务
        w = new Worker(firstTask);
        // 新建 Thread,Worker 线程就包装在这个 Thread 里面
        final Thread t = w.thread;
        if (t != null) {
            // 加锁
            final ReentrantLock mainLock = this.mainLock;
            // 加锁成功标志
            boolean workersAdded = false;
            try {
                // 将新建的线程添加到 HashSet<Worker> workers 中,然后再加到 HashSet<Thread> threads 中,也就是 t 线程加入线程池
                mainLock.lock();
                // Recheck while holding lock.
                // Back out on ThreadFactory failure or if
                // shut down before lock acquired.
                // 再次检查线程池终止状态是否被改变,若被改变则不再增加一个 Worker 线程
                int rs = runStateOf(ctl.get());
                if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 将 w 添加到 HashSet<Worker> workers 中
                    workers.add(w);
                    int s = workers.size();
                    // 将 workersAdded 标志设置为 true
                    workersAdded = true;
                    // 如果新建的 Worker 线程数量超过 maximumPoolSize,那么将最大线程数修改为实际数量
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                }
            } finally {
                // 释放 mainLock 锁
                mainLock.unlock();
            }

            // 如果 workersAdded 标志为 true,说明 Worker 线程已添加到线程池中,这里开始启动线程
            if (workersAdded) {
                // 真正启动 Worker 线程
                t.start();
                // workerStarted 标志设置为 true
                workerStarted = true;
            }
        }
    } finally {
        // 确保 Worker 线程已启动成功
        if (!workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

=== 点击查看top目录 ===

六、番外篇

上一章节:【线程】ThreadPool 线程池 Executors 实战 (十九)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值