JUC Executor-ThreadPoolExecutor(线程池) 源码解析

通过Java并发编程实战(进阶篇 - 下)对 Executor 框架的简介,已经基本了解了 Executor 框架的结构和成员,本篇讲解 Executor 框架中任务执行单元 ThreadPoolExecutor(线程池)工作原理。

前言

提出三个问题来更好的了解 ThreadPoolExecutor:

  1. 什么是线程池?
    线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

  2. 为什么要使用线程池?
    因为创建和贤惠线程都是需要时间的,特别是需要创建大量线程的时候,时间和资源的消耗是不可忽略的,而合理的使用线程池中已经创建的线程,可以减少创建和销毁线程而花费的时间和资源。

  3. 线程池的优点?
    (1)降低资源消耗:通过线程的重用可以降低创建和销毁线程花费的时间和资源;
    (2)提高响应速度:任务到达时,因为利用线程池中已经创建好的线程,可以不用等待线程创建而直接执行任务;
    (3)提高线程的可管理性:线程池允许我们开启多个任务而不用为每个线程设置属性(便于管理);线程池根据当前在系统中运行的进程来优化线程时间片(调优);线程池可以限制创建线程的数量,如果无限制的创建线程,不仅会消耗资源,还会降低系统的稳定性;

一、线程池的使用

一般没有特殊需求的情况下,可以使用 Executors 工厂类提供的FixedThreadPool、SingleThreadExecutor、CacheThreadPool这三种线程池,在Java并发编程实战(进阶篇 - 下)也已经讲到。如果需要对线程池的各个参数精确的要求,就可以使用 ThreadPoolExecutor 的构造方法来创建,下面会讲到。

二、线程池的实现原理

线程池的实现原理非常清晰,当向线程池提交一个任务时,大致分为3步:

  1. 线程池判断核心线程池(corePoolSize)里的线程是否都在执行任务
    否:创建一个新的工作线程来执行任务;
    是:进入下一个流程。
  2. 线程池判断工作队列(BlockingQueue)是否已经满。
    否:将新提交的任务存储在这个工作队列里;
    是:进入下一个流程。
  3. 线程池判断线程池的线程(maximumPoolSize)是否都处于工作状态。
    否:创建一个新的工作线程来执行任务;
    是:交给饱和策略来处理这个任务。
  4. 饱和策略(RejectedExecutionHandler)处理任务。

线程池的主要处理示意图:
在这里插入图片描述
ThreadPoolExecutor 执行示意图:
在这里插入图片描述
通过示意图可以直观的了解到线程池的工作原理,接下来通过 ThreadPoolExecutor 源码分析来进一步了解线程池的实现细节。

三、ThreadPoolExecutor 源码分析

1.ThreadPoolExecutor 类图与接口

在这里插入图片描述

  • Exector 接口:运行新任务的简单接口
  • ExectorService 接口:扩展了 Exector 接口,添加了一些用来管理执行器生命周期和任务生命周期的方法
  • ScheduledExecutorService 接口:扩展自ExectorService接口,支持Future和定期执行任务
  • Exectors 类:包装了具体的几个常用的线程池的定义,便于使用

2.重要属性

/**
 * 核心线程大小。
 * 	当提交一个任务到线程池时,线程池会创建一个线程来执行,即使其他空闲的核心线程能够执行
 * 	新任务也会创建线程,等到需要执行的任务数大于线程池核心大小时就不在创建。
 * 	如果调用了线程池的 prestartAllCoreThreads() 方法,线程池会提前创建并启动所有核心线程
 */
private volatile int corePoolSize;
/**
 * 用于保存等待执行的任务的阻塞队列。
 * 	4种阻塞队列:
 * 		ArrayBlockingQueue:基于数组的有界阻塞队列
 * 		LinkedBlockingQueue:基于链表结构的阻塞队列,吞吐量高于ArrayBlockingQueue,
 * 			静态工厂方法:Executors.newFixedThreadPool()使用这个队列
 * 		SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用
 * 			移除操作,否则插入操作一致阻塞,吞吐量高于LinkedBlockingQueue
 * 			静态工厂方法:Executors.newCachedThreadPool()使用这个队列
 * 		PriorityBlockingQueue:具有优先级的无限阻塞队列。
 */
private final BlockingQueue<Runnable> workQueue;
/**
 * 线程池允许创建的最大线程数。
 * 	如果队列满了,并且已创建的线程数小于最大线程数,则线程池会在创建新的线程执行任务。
 * 	
 * 需要注意:如果使用了无界的任务队列这个参数就没有效果。
 */
private volatile int maximumPoolSize;
/**
 * 用于设置创建线程的工厂,通过实现ThreadFactory接口的newThread()方法来自定义创建线程
 * 	默认使用Executors.defaultThreadFactory()方法创建的工厂
 */
private volatile ThreadFactory threadFactory;
/**
 * 饱和策略:当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交
 * 的新任务。默认策略-AbortPolicy,表示无法处理新任务时抛出异常。
 * 	private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
 * 		AbortPolicy:直接抛出异常
 * 		CallerRunsPolicy:只用调用者所在线程来运行任务
 * 		DiscardOldestPolicy:丢弃队列里最前面的一个任务,并执行当前任务
 * 		DiscardPolicy:不处理,丢弃掉
 * 
 * 也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。
 */
private volatile RejectedExecutionHandler handler;
/**
 * 线程池的工作线程空闲后,保持存活的时间。
 * 	如果任务很多,并且每个任务执行的时间比较短,可以调大时间,调高线程的利用率。
 */
private volatile long keepAliveTime;

线程池状态:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;	// 29
// 高3位为0,低29位为1
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
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;

// Packing and unpacking ctl
// 传入ctl计算runState
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; }

ctl字段是组合为线程池的运行状态和有效线程数异或生成的,高三位保存了runState(运行状态),低29位保存了workerCount(工作线程个数)

RUNNING : 能接受新提交的任务,并且能处理阻塞队列中的任务

SHUTDOWN : 关闭线程池,不再接受新提交的任务,但是可以继续处理在阻塞线程中已经存在的任务,
	线程在 RUNNING 状态调用shutdown()方法进入到SHUTDOWN状态

STOP : 不接受新任务,也不处理阻塞队列的任务,中断正在处理的线程,线程处于 RUNNING 或者 
	SHUTDOWN 状态,调用 shutdownNow()方法进入到STOP 状态

TIDYING:所有的任务都终止了,workCount为0,线程池如果处于该情况下,调用terminated()方法
	进入到 TERMINATED 状态

3.构造方法

一般情况下,使用线程池可能会使用 Executors 工厂类来创建线程池。

public class Executors {
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}

默认由executors创建的线程池的工作队列都是基于链表的阻塞队列,没有具体长度。想要更加灵活的线程池,建议使用构造方法来为其设置参数来创建。

public class ThreadPoolExecutor extends AbstractExecutorService {
	...
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
            BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
        BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    ...
}

4.任务提交

4.1 execute(Runnable command)

向线程池提交command任务。

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.
     *
     * 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.
     *
     * 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.
     */
    int c = ctl.get();
    // 1.工作线程数量小于核心线程数量,创建线程执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2.工作线程数量大于核心线程数量并且线程池正在运行,尝试将任务添加到任务队列
    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);
    }
    // 3.任务队列已满,再次尝试增加线程,失败则将任务交给饱和策略处理
    else if (!addWorker(command, false))
        reject(command);
}

4.2 addWorker(Runnable firstTask, boolean core)

创建线程执行提交任务。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
		
        // Check if queue empty only if necessary.
        /**
         * 1.rs > SHUTDOWN:直接返回false,不执行新任务,不执行任务队列的任务
         * 2.rs == SHUTDOWN && firstTask != null:直接返回false,拒绝处理新任务
         * 3.rs == SHUTDOWN && firstTask == null && workQueue为空:直接返回false
         * 		workQueue没有任务执行,就没有创建工作线程的必要
         */
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;
		
		// CAS将工作线程数+1,如果ctl状态改变,即线程池状态发生变化,从外层循环重新执行
        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
        }
    }
	
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        /**
         * Worker(Runnable firstTask) {
         *   setState(-1); // inhibit interrupts until runWorker
         *   this.firstTask = firstTask;
         *   // thread 通过工厂方法创建
         *   this.thread = getThreadFactory().newThread(this);
         * }
         */
        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());
                // 如果线程处于运行中或 rs == SHUTDOWN && firstTask==null,
                // 创建一个新的工作线程 
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // 线程还未启动就存活,非法异常
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    // 加入到工作线程集合中    
                    workers.add(w);
                    int s = workers.size();
                    // 更新最大的工作线程个数
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 启动工作线程,运行Worker中的runWorker方法
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
    	// 启动失败:1.线程工厂创建线程失败;2.在获取锁之前就关闭了线程池
        if (! workerStarted)
        	// 移除掉添加到工作集合的worker,并尝试终止线程池
            addWorkerFailed(w);
            /*
             *   private void addWorkerFailed(Worker w) {
             *       final ReentrantLock mainLock = this.mainLock;
             *       mainLock.lock();
             *       // 依旧可能存在线程安全问题
             *       try {
             *           if (w != null)
             *               workers.remove(w);
             * 			 // 工作线程数量-1
             *           decrementWorkerCount();
             *           tryTerminate();
             *           // 尝试更新线程池状态到TERMINATED 状态
             *       } finally {
             *           mainLock.unlock();
             *       }
             *   }
             */
    }
    return workerStarted;
}

4.3 runWorker(Worker w)

在创建工作线程 Worker 时,因为 Worker 实现了 Runnable 接口,所以将自己 this 传入到成员变量thread中。

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    // 将当前worker对象作为参数传入,使用工厂方法创建线程
    this.thread = getThreadFactory().newThread(this);
}

所以在addWorker方法中启动线程t.start();,其实是调用了 Worker 中的run()方法:

public void run() {
    runWorker(this);
}

所以真正的执行任务在runWorker方法中:

final void runWorker(Worker w) {
	// 获取当前启动线程,用来判断中断
    Thread wt = Thread.currentThread();
    // 工作线程需要执行的任务
    Runnable task = w.firstTask;
    w.firstTask = null;
    // 让当前同步器的state状态重置为0,允许中断
    // 在Worker构造方法中将state置为了-1,不允许工作线程中断
    // setState(-1); // inhibit interrupts until runWorker
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    	// 存在任务,该工作线程就一直执行任务
        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
            // 如果线程池正在停止,那么要保证当前线程是中断状态
            // 否则要保证当前线程不是中断状态(在RUNNING 或 SHUTDOWN 状态下,
            // 调用 Thread.interrupted() 可以恢复中断装填,该线程需要执行完任务)
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
            	// 可以实现beforeExecute方法自定义执行任务之前的操作
                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;
                // 记录当前工作线程完成任务数量
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

4.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.
        /**
         * 1.rs >= SHUTDOWN:线程池处于SHUTDOWN,可以执行任务队列的任务
         * 	如果: rs >= STOP:停止执行任务队列任务,工作线程-1,直接返回null
         * 	如果:workQueue.isEmpty():任务队列为空,也不需要执行任务,直接返回null
         */
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        /**
         * 核心工作线程是否需要被淘汰?
         * 	allowCoreThreadTimeOut:
         * 		默认false:即使核心线程空闲也不会被淘汰,没有超时控制
         * 		true:超过核心线程数量的线程需要超时控制
         */
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
		
		// 允许淘汰工作线程
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
		
		// 可能会有多个工作线程去任务队列中获取任务,但是阻塞队列底层使用了ReentrantLock
		// 来保证并发访问时的线程安全性
        try {
        	// timed=true:超时获取任务(poll方法进行超时控制,如果在keepAliveTime时间内没有获取到任务,则返回null)
        	// timed=flase:阻塞获取任务,直到线程获取到任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            // 为获取到任务:1.任务队列为空 2.超时
            // 这里设置超时获取任务,再次获取任务    
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

4.5 其他方法

processWorkerExit(Worker w, boolean completedAbruptly):在runWorker中被调用,当无法获取到有效执行的task任务,或执行任务期间抛出异常导致worker无法执行,该方法将worker 销毁。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // true:在执行任务中出现了异常导致,将工作线程数-1
    // false:表示无法获取到有效任务(任务队列为空),会在getTask方法中淘汰工作线程
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();
	
	// 加锁,对工作线程集合独占使用,由于HashSet不是并发安全的容器
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    	// 记录总的任务完成数
        completedTaskCount += w.completedTasks;
        // 将当前worker从集合中去除
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

	// 尝试将线程池状态变更为TERMINATED
    tryTerminate();

    int c = ctl.get();
    // 线程池处于 RUNNING 或者 SHUTDOWN 状态
    if (runStateLessThan(c, STOP)) {
    	// 当前工作线程是由于在runWork方法中没有有效任务而退出的
        if (!completedAbruptly) {
        	// 根据 allowCoreThreadTimeOut 判断线程池的核心线程是否设置了超时等待
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            // 如果设置了超时等待并且任务队列不为空,min置为1
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            // 工作线程数量大于min(1或核心线程数量)    
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 创建一个工作线程,防止工作线程数量为0
        addWorker(null, false);
    }
}

tryTerminate():尝试将将线程池状态变为 TERMINATED,终止线程池。

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        /**
         * 1.处于运行状态 RUNNING
         * 2.处于 TIDYING 或者 TERMINATED 状态
         * 3.处于 SHUTDOWN 并且工作线程不为空
         * 这三种情况直接返回 
         */
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        // 工作线程数不为0,中断获取任务的工作线程(getTask获取中断,工作线程-1,并返回null)
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
        	// 将线程池置为 TIDYING 状态
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                	// 如果CAS成功,就将线程池置为 TERMINATED 状态
                    ctl.set(ctlOf(TERMINATED, 0));
                    // 唤醒超时等待的工作线程
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

四、线程池的配置

(1)CPU(计算)密集型则线程数配置尽可能的少,比如NCPU+1。可以通过Runtime.getRuntime().avaliableProcessors( )方法获得当前设备的CPU数量;

(2)IO密集型需要配置尽可能多的线程数,比如2*NCPU,因为IO处理时线程阻塞的时间很长,导致CPU空闲时间很长,多一点线程可以提高CPU利用率;

(3)混合型任务:如果可以拆分,最好拆分成CPU密集型任务+IO密集型任务,只要这两个拆分后的任务执行时间相差没有太大,那么拆分后的吞吐量将高于串行执行的吞吐量,如果时间相差太大,就没有必要分解;

(4)优先级不同的任务:使用PriorityQueue作为阻塞队列。(如果一直有优先级高的任务进来,可能导致优先级低的任务无法执行)

(5)执行时间不同的任务:可以交给不同规模的线程池来执行;或者使用PriorityQueue作为阻塞队列,把执行时间短的任务优先级设置高一点,让时间短的任务先执行;

(6)建议使用有界队列,这样可以保证系统的稳定性,如果队列时无界的,那么一直有任务进来就一直往阻塞队列添加节点,可能导致内存溢出。

还未彻底了解的可以参考这篇博客:深入理解Java线程池

发布了120 篇原创文章 · 获赞 16 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43327091/article/details/104624516