多线程 - 线程池ThreadPoolExecutor

本文深入探讨了Java中的线程池实现类ThreadPoolExecutor,详细阐述了线程池的工作原理,包括线程池的创建、任务调度、Worker线程、任务缓冲、拒绝策略以及线程池的关闭方法。还分析了四种内置的拒绝策略,并给出了创建线程池的四种常见模式及其适用场景。最后,讨论了如何监控和优化线程池,以及线程池状态转换的过程。
摘要由CSDN通过智能技术生成

前言

随着计算机行业的飞速发展,多核CPU成为主流。使用多线程并行计算逐渐成为开发人员提升服务器性能的基本武器。J.U.C提供的线程池ThreadPoolExecutor类,帮助开发人员管理线程并方便地执行并行任务。了解并合理使用线程池,是一个开发人员必修的基本功。

1. 线程池

线程池(Thread Pool)是一种基于池化思想管理线程的工具。使用线程池可以带来一系列好处:

  • 降低资源消耗:重用已存在的线程,降低线程创建和销毁造成的消耗;
  • 提高响应速度:无需等待新线程的创建便能立即执行;
  • 提高线程可管理性:提供了一种限制和管理资源(包括线程)的方法,适用不同的场景,使得性能提升明显;
  • 提供更强大的功能:线程池提供统计数据,比如当前线程池完成的任务数目;

“池化”思想不仅仅能应用在计算机领域,在金融、设备、人员管理、工作管理等领域也有相关的应用。在计算机领域中的表现为:统一管理IT资源,包括服务器、存储、和网络资源等等。通过共享资源,使用户在低投入中获益。除去线程池,还有其他比较典型的几种使用策略包括:

  • 内存池(Memory Pooling):预先申请内存,提升申请内存速度,减少内存碎片。
  • 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
  • 实例池(Object Pooling):循环使用对象,减少资源在初始化和释放时的昂贵损耗。

2. ThreadPoolExecutor探究

线程池在java中的核心实现类是TreadPoolExecutor.

2.1 Overview

在这里插入图片描述
由上图可见,在java的线程池中主要有以下几个模块:

  • 任务调度 - Execute worker
  • Worker线程 - Worker
  • 任务缓冲 - WorkerQueue
  • 拒绝策略 - RejectedExecutionHandler

2.2 任务调度

所有任务的调度都是由execute方法完成的,这部分完成的工作是:
情况1:如果workerCount < corePoolSize,则创建并启动一个Worker线程来执行当前任务。
情况2:如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。然后重新检查线程池是否为运行状态;
若在将当前任务放入阻塞队列的过程中,线程池状态已经被关闭,则需将当前任务从任务队列中移除,并执行拒绝策略;若当前任务移除失败且无worker线程,则创建一个worker线程。(猜测可能是线程池被关闭前当前任务也被出队执行了,所以remove失败,不需要执行拒绝策略,只是再没有worker线程时增加一个确保任务队列中的任务可以被消费即可。)
如果workerCount >= corePoolSize 且线程池内的阻塞队列已满 且 workerCount < maximumPoolSize,则创建并启动一个线程来执行当前任务。
情况3:如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

2.2.1 code

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    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))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

2.3 Worker

每个worker都在自己持有的线程中执行,会从阻塞队列中取得任务并执行该任务的run()方法。

private final HashSet<Worker> workers = new HashSet<Worker>();
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        final Thread thread; //worker持有的线程,用于执行firstTask以及任务队列中的任务
        Runnable firstTask; //初始化当前worker的任务,
        volatile long completedTasks;//统计执行完成的任务数量

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

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        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(); }
        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

2.3.1 增加Worker

下面代码有点长,大概分两步:
step1: 双重循环:第一重循环检查线程池状态; 第二重循环:检查线程数量,通过CAS增加线程数;当CAS失败,重新进行CAS前,需要重新检查线程池状态是否变化,若没变,则重新第二重循环,只需要检查线程数量;若线程池状态发生变化,则需要重第一重循环开始执行,目的是重新做线程池状态的检查。
step2: 创建一个工作线程Worker;
加锁,把新创建的worker线程添加到workers,检查是否需要更新workers的最大size;解锁;
对Worker中的线程调用start()方法,即,启动该线程,因为该线程中的target就是当前新创建的worker线程,故调用start()方法实际就是调用了worker的run()方法。

private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        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);
        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());

                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();
            }
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

2.3.2 worker的执行

一旦开启了worker,则将worker unlock,允许中断线程执行。若task不为null,为当前work首次执行,即执行的是创建该worker的任务,否则调用getTask()从任务队列获取任务。若task不为空,则获取worker内部的独占lock,然后执行具体的任务,并统计将worker中的completedTasks自增1;释放worker内部锁。
再执行具体任务期间加锁,为了避免在具体任务执行期间,其他线程调用了shutdown后正在执行的worker被中断。
当worker无法获取任务时,回收worker。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();//这个是worker中的线程
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // 一旦开启了worker,则将worker unlock,允许中断线程执行。
    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
            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;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);//回收worker
    }
}

2.3.3 Worker回收

只在worker的线程中被调用,当worker在执行过程中无法从getTask()中获得任务时被调用。
step1: 将移除的worker完成的任务数量completedTasks累加至线程池完成任务计数器completedTaskCount;
step2: 将worker从workers中移除(消除worker在线程池workers内的引用);

至此,线程池就已经结束了线程销毁的部分。但由于引起线程销毁的可能性有很多,线程池还要判断是什么引发了这次销毁,是否要改变线程池的现阶段状态,是否要根据新状态,重新分配线程。

step3: 尝试中止线程池
step4: 判断当前线程池中worker的数量是否小于核心线程数,若小于则增加一个worker。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        completedTaskCount += w.completedTasks;
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);
    }
}
2.3.1 从阻塞队列中获取任务

当worker在执行过程中无法从getTask()中获得任务时回收线程,所以我们主要关注何时会返回null
减少一个worker并返回null的场景:
场景一:
线程池的状态是SHUTDOWN且阻塞队列为空 线程池的状态是STOP,TIDYING, TERMINATED;
场景二:
非核心线程在阻塞队列上等待超时且阻塞队列为空

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.
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        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();//实现线程复用的关键,当阻塞队列空时,worker一直在这里等待,不会被销毁
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}


private void decrementWorkerCount() {
    do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}

2.4 阻塞队列

2.5 拒绝策略

触发拒绝策略需在提交任务时,同时满足以下条件:

  • 核心线程数达到上限
  • 任务队列达上限
  • 最大线程数达上限
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

当触发拒绝策略时,线程池会调用你设置的具体的策略,将当前提交的任务以及线程池实例本身传递给你处理,具体作何处理,不同场景会有不同的考虑,下面看JDK为我们内置了哪些实现:

2.5.1 ThreadPoolExecutor内置的拒绝策略

2.5.1.1 CallerRunsPolicy
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

使用场景:一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。

2.5.1.2 AbortPolicy
public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}
2.5.1.3 DiscardPolicy
public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

使用场景:如果你提交的任务无关紧要,你就可以使用它 。

2.5.1.4 DiscardOldestPolicy

如果线程池未关闭,就弹出队列头部的元素,然后尝试执行

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

使用场景:这个策略还是会丢弃任务,丢弃时也是毫无声息,但是特点是丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。

2.5.2 activeMq中的线程池拒绝策略

new RejectedExecutionHandler() {
    @Override
    public void rejectedExecution(final Runnable r, final ThreadPoolExecutor executor) {
        try {
            executor.getQueue().offer(r, 60, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RejectedExecutionException("Interrupted waiting for BrokerService.worker");
        }

        throw new RejectedExecutionException("Timed Out while attempting to enqueue Task.");
    }
});

activeMq中的策略属于最大努力执行任务型,当触发拒绝策略时,在尝试一分钟的时间重新将任务塞进任务队列,当一分钟超时还没成功时,就抛出异常

2.5.3 Netty中的线程池拒绝策略

private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
    NewThreadRunsPolicy() {
        super();
    }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        try {
            final Thread t = new Thread(r, "Temporary task executor");
            t.start();
        } catch (Throwable e) {
            throw new RejectedExecutionException(
                    "Failed to start a new thread", e);
        }
    }
}

Netty中的实现很像JDK中的CallerRunsPolicy,舍不得丢弃任务。不同的是,CallerRunsPolicy是直接在调用者线程执行的任务。而 Netty是新建了一个线程来处理的。

2.6 Executors创建线程池

2.6.1 种类及使用场景

newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用worker来执行,若有可以线程则执行,若没有创建一个worker来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器

newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

若核心线程数量达上限,阻塞队列未满则新提交的任务会进入阻塞队列中(无界的阻塞队列)。
适用:执行长期的任务

newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

创建只有一个线程的线程池,对于新提交的任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景

NewScheduledThreadPool
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

创建一个固定大小的线程池,线程池可以支持定时及周期性任务执行,如果核心线程数量达上限,对于新任务会进入DelayedWorkQueue队列中,这是一个无界阻塞队列,按照过期时间排序的优先队列,对头元素时最快要过期的元素。
适用:周期性执行任务的场景

2.6.2 为什么不推荐Executors创建线程池?

最可能的问题是发生内存溢出。任务是会一直往队列里面放的,放不下了才可能开始拒绝,但newFixedThreadPool、newSingleThreadExecutor这些线程池传入的队列都是LinkedBlockingQueue,也就是无界队列,缓存的任务数可能会无限增长。newCachedThreadPool这个线程池倒是没有使用无界队列而是使用一个即进即出的队列,但是这个线程池的最大线程数是Integer.MAX_VALUE,线程数会无限增长,同样会内存溢出。

2.7 优雅关闭

2.7.1 shutdown

启动一个有序的关闭,先前提交的任务将被执行,但不接受新的任务。 如果已经关闭,则调用没有额外效果。该方法立即返回。 使用 awaitterminate 来确保完成线程池关闭。

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

CAS更改ctl为SHUTDOWN状态,若已经为SHUTDOWN&STOP&TIDYING&TERMINATED,则什么都不做

/**
 * Transitions runState to given target, or leaves it alone if already at least the given target.
 *
 * @param targetState the desired state, either SHUTDOWN or STOP
 *        (but not TIDYING or TERMINATED -- use tryTerminate for that)
 */
private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

然后调⽤interruptIdleWorkers方法,来中断空闲的线程。这是interruptIdleWorkers⽅法的实现:

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

在所有线程都在等待的情况下,最多会中断一个正在等待的worker来传播关闭信号。
当阻塞队列为空,worker在阻塞队列上等待获取任务时被中断,并CAS更新ctl的woker数量减1,getTask()返回null, runWoker()方法调用processWorkerExit()方法,进行worker回收,进行worker回收时会调用tryTerminate(),tryTerminate方法进行中断传播,即发现当还有worker未被回收时,继续中断空闲worker,即调用interruptIdleWorkers方法(有点类似interruptIdleWorkers被递归调用了的意思,但是是在不同的方法), addWorker()在状态检查时,当发现线程池被shutdown且任务队列为空时退出。正是因为在进行worker回收时会调用tryTerminate(), 所以只中断一个空闲的worker也可以使所有的worker都被中断,但是shutdown()选择中断所有空闲的workers,这样多余的workers就会立即退出。

2.7.2 shutdownNow

将线程池状态修改为STOP,然后调⽤线程池⾥的所有worker的interrupt⽅法。

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    final Thread thread;
    
    .............//省略Worker的其他代码
    void interruptIfStarted() {
        Thread t;
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            }
        }
    }
}

worker的主要工作:runWorker()方法,在被中断前worker会在这个while循环里进行。其中代码task.run()就是在执⾏线程池的任务:
若当调用shutdownNow 时,检查worker中持有的线程是否已被中止,若还未被中止,则中止该线程;
若当调用shutdownNow 时,task.run()⾥⾯正处于IO阻塞,则会导致报错,如果task.run()⾥正在正常执
⾏,则不受影响,继续执⾏完这个任务。

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    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
            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;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

任务完成后会再次调用getTask方法,在getTask方法中,被中止的线程会被该方法中的catch块捕获,被捕获后继续在for循环中执行,并检查线程池状态,若发现线程池状态为STOP,无论阻塞队列是否为空,均会将ctl(worker数量) CAS自减1,并返回null。使得推出while循环,进行线程回收。后面于shutdown方法流程相同。

shutdownNow不管阻塞队列是否有未完成的任务均会更改worker数量,回收线程,继续中断其他空闲线程。如果worker因为执行任务而处于阻塞状态,则会导致报错,否则不受影响直到执行完毕。
shutdown需等待阻塞队列为空才会更改worker数量,回收线程,继续中断其他空闲线程。正在执行任务的worker不受影响。
所以当我们使⽤shutdownNow⽅法关闭线程池时,一定要进行异常捕获。
当我们使用shuwdown方法关闭线程池时,一定要确保任务里不会有永久阻塞等待的逻辑,否则线程池就关闭不了。
最后,⼀定要记得shutdownNow和shuwdown调用完,线程池并不是⽴刻就关闭了,要想等待线程池关闭,还需调用awaitTermination⽅法来阻塞等待。当该方法返回false,只说明等待超时,所以要将其放入while循环中不断检测,直至其返回true,证明线程池被完全关闭。

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}

termination是一个条件,在tryTerminate()中会更改线程池的状态为TERMINATED,然后调用signalAll()通知所有等待该信号的线程。

/**
 * Wait condition to support awaitTermination
 */
private final Condition termination = mainLock.newCondition();

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

2.7.3 线程池状态转换

RUNNING -> SHUTDOWN : 调用shutdown() 或finalize()方法时
RUNNING -> STOP: 调用shutdownNow()时
SHUTDOWN -> TIDYING: 阻塞队列为空 且 worker数量为0时
STOP -> TIDYING: worker数量为0时
TIDYING -> TERMINATED: 当terminated() hook方法执行完成时

3. 实践

3.1 监控

用户基于JDK原生线程池ThreadPoolExecutor提供的几个public的getter方法,可以读取到当前线程池的运行状态以及参数:
在这里插入图片描述
通过对这些参数的计算可以监控线程池的任务执行情况,例如:最大任务执行时间、平均任务执行时间、线程池活跃度 、线程池负载等信息。
线程池活跃度 :activeCount/maximumPoolSize。当活跃线程数趋向于maximumPoolSize的时候,代表线程负载趋高
线程池负载:发生Reject异常次数的统计,队列中等待任务的统计。

3.2 优化

线程等待时间所占比例越高,需要越多线程。
线程CPU时间所占比例越高,需要越少线程,设置的线程数过多会引发线程上下文切换频繁的问题,降低处理任务的速度,降低吞吐量。
核心线程数量设置过小,导致任务执行速率变低;若阻塞队列为无界队列,则最大线程可能失效。当提交的任务核心线程无法及时处理时,引起任务队列的任务堆积。

场景1:快速响应用户请求
这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,即,使用同步队列,没有什么任务应该被缓存下来,而是应该立即执行。调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务。
场景2:快速处理批量任务
吞吐量优先,使用有界队列去缓冲并发任务,队列容量必须声明,防止任务无限制堆积。调整合适的corePoolSize去设置处理任务的线程数,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。
JDK原生线程池ThreadPoolExecutor提供了如下几个public的setter方法:
在这里插入图片描述
通过线程池的名字找到指定的线程池,然后对其参数进行修改,保存后会实时生效。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值