九、线程池 Executor

九、线程池 Executor

1、Java线程理解

线程是调度CPU的最小单元,也叫轻量级进程 LWP( Light Weight Process)

小拓展:两种线程模型:用户级线程(ULT)、内核级线程(KLT)

用户线程(ULT):用户程序实现,不依赖操作系统核心,应用提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/内核态切换,速度快。内核对ULT无感知,线程阻塞则进程(包括它的所有线程)阻塞。

内核线程(KLT):系统内核管理线程(KLT),内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多处理器系统上,多线程在多处理器上并行运行。线程的创建、调度和管理由内核完成,效率比ULT要慢,比进程操作快。

JVM 用的是 KLT!


2、线程池的意义

线程是稀缺资源,它的创建与销毁是一个相对偏重且耗资源的操作,而 Java 线程依赖于内核线程,创建线程需要进行 操作系统状态切换,为避免资源过度消耗需要设法 重用线程 执行多个任务。线程池就是一个线程缓存,负责对线程进行统一分配、调优与监控。

▼什么时候使用线程池?

  1. 单个任务处理时间比较短
  2. 需要处理的任务数量很大

▼线程池优势

  1. 重用存在的线程,减少线程创建、销毁的开销,提高性能
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行
  3. 提高线程的可管理性,可统一分配、调优和监控

3、Executor

  1. 背景:经常创建、销毀、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

  2. 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中可以避免频繁创建、销毀,实现重复利用。类似生活中的公共交通工具

  3. 好处

    1. 提高响应速度(减少了创建新线程的时间)
    2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    3. 便于线程管理,核心参数如下:
      1. corePoolSize:核心线程数
      2. maximumPoolsize:最大线程数(核心线程 + 非核心线程)
      3. keepAliveTime:非核心线程没有任务时最多保持多长时间后会销毁
      4. timeUnit:时间单位
      5. workQueue:阻塞队列
      6. threadFactory:创建线程的工厂
      7. handler:拒绝策略
  4. JDK 5.0 起提供了线程池相关API:ExecutorserviceExecutors

  5. ExecutorService:真正的线程池接口。常见子类 ThreadPooExecutor

    1. void execute(Runnable command):只能提交 Runnable类型 的任务,无返回值
    2. <T> Future<T> submit( Callable<T>task):既能提交 Runable类型 的任务,返回值为null,也能提交 Callable类型 的任务,返回值为Future
    3. void shutdown():关闭连接池
    4. void shutdownNow():立即关闭连接池
  6. Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    1.在这里插入图片描述

  7. Executor 类图
    1.在这里插入图片描述


4、线程池的五种状态

  1. Running
    1. 能接受新任务以及处理已添加的任务
  2. Shutdown
    1. 不接受新任务,可以处理已经添加的任务
  3. Stop
    1. 不接受新任务,不处理已经添加的任务,并且中断正在处理的任务
  4. Tidying
    1. 所有的任务已经终止,ct1记录的“任务数量”为0,ct1负责记录线程池的运行状态与活动线程数量
  5. Terminated
    1. 线程池彻底终止,则线程池转变为 terminated状态

1、CODE

public class ThreadPoolExecutor extends AbstractExecutorService {
    /**
     * 主池控制状态ctl是一个包含两个概念字段的原子整数
     *   workerCount:指示有效线程数
     *   runState:指示是否正在运行、关闭等
     *
     * 为了将它们打包成一个整数,我们将 workerCount 限制为
     * (2^29)-1(约5亿)个线程,而不是(2^31)-1(20亿)个其他可
     * 表示的线程。如果将来出现问题,可以将变量更改为原子长,并调整下面
     * 的 移位/掩码 常数。但是,在需要之前,使用int会使代码更快、更简单
     *
     * workerCount 是允许启动和不允许停止的工人数。该值可能暂时不同于
     * 活动线程的实际数量,例如,当 ThreadFactory 在被请求时无法创建
     * 线程时,以及当退出线程在终止之前仍在执行簿记时。用户可见池大小报
     * 告为工作集的当前大小
     *
     * 运行状态提供主要的生命周期控制,具有以下值:
     *   RUNNING:  接受新任务并处理排队的任务
     *   SHUTDOWN: 不接受新任务,但处理排队的任务
     *   STOP:     不接受新任务,不处理排队的任务,
     *             以及中断正在进行的任务
     *   TIDYING:  所有任务都已终止, workerCount 为零,
     *             转换到状态“TIDYING”的线程将运行 
     *             terminated()钩子方法
     *   TERMINATED: terminated() 已经完成
     *
     * 这些值之间的数字顺序很重要,以便进行有序比较。运行状态随时间单调增加,
     * 但不需要达到每个状态。过渡是:
     * RUNNING -> SHUTDOWN
     *    在调用 shutdown() 时,可能隐式地在 finalize() 中
     * (RUNNING or SHUTDOWN) -> STOP
     *    在调用 shutdownNow() 时
     * SHUTDOWN -> TIDYING
     *    当 queue 和 pool 都为空时
     * STOP -> TIDYING
     *    当 pool 为空时
     * TIDYING -> TERMINATED
     *    当 terminated() 钩子方法完成时
     *
     * 等待 awaitTermination() 的线程将在状态达到 TERMINATED 时返回
     *
     * 检测从 SHUTDOWN 到 TIDYING 的转换并不像您希望的那样简单,因为队列
     * 在非空状态后可能会变为空,而在 SHUTDOWN 状态下也可能变为空。但我们
     * 只能在看到队列为空后,看到 workerCount 为 0 时终止(这有时需要重新
     * 检查——见下文)
     */
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
    // 常量 29
    private static final int COUNT_BITS = Integer.SIZE - 3;
    
    // 低29位 存储 当前工作线程数
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // 高3位 存储 运行状态
    /** 
     * -1 = 1111 1111 1111 1111 1111 1111 1111 1111
     * -1 << 29 = 1110 0000 0000 0000 0000 0000 0000 0000
     * 高3位 = 111 ==> RUNNING
     */
    private static final int RUNNING    = -1 << COUNT_BITS;
    /** 
     * 0 = 0000 0000 0000 0000 0000 0000 0000 0000 
     * 0 << 29 = 0000 0000 0000 0000 0000 0000 0000 0000
     * 高3位 = 000 ==> SHUTDOWN
     */
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    /** 
     * 1 = 0000 0000 0000 0000 0000 0000 0000 0001 
     * 1 << 29 = 0010 0000 0000 0000 0000 0000 0000 0000
     * 高3位 = 001 ==> SHUTDOWN
     */
    private static final int STOP       =  1 << COUNT_BITS;
    /** 
     * 2 = 0000 0000 0000 0000 0000 0000 0000 0010 
     * 2 << 29 = 0100 0000 0000 0000 0000 0000 0000 0000
     * 高3位 = 010 ==> TIDYING
     */
    private static final int TIDYING    =  2 << COUNT_BITS;
    /** 
     * 3 = 0000 0000 0000 0000 0000 0000 0000 0011 
     * 3 << 29 = 0110 0000 0000 0000 0000 0000 0000 0000
     * 高3位 = 011 ==> TERMINATED
     */
    private static final int TERMINATED =  3 << COUNT_BITS;
    
}

2、状态转换

在这里插入图片描述


5、execute

1、CODE

public class ThreadPoolExecutor extends AbstractExecutorService {
    // ...

    /**
     * 在将来的某个时间执行给定的任务。该任务可以在新线程或现有池线程中执行。
     *
     * 如果由于此执行器已关闭或已达到其容量而无法提交任务以供执行,
     * 则该任务将由当前 {RejectedExecutionHandler} 处理。
     *
     * 参数:command 要执行的任务
     * 抛出:
     * RejectedExecutionException 如果无法接受任务执行,则由{RejectedExecutionHandler}决定
     * NullPointerException 如果命令为空
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * 分3步进行:
         *
         * 1. 如果运行的线程少于 corePoolSize,请尝试使用给定命令作为其第一个任务启动新线程。
         *    对addWorker的调用以原子方式检查运行状态和workerCount,因此通过返回false来
         *	  防止在不应该添加线程时添加线程的错误警报。
         *
         * 2. 如果一个任务可以成功地排队,那么我们仍然需要仔细检查是否应该添加一个线程(因为
         *	  存在自上次检查以来已有的线程已死亡),或者自进入此方法后池是否已关闭。因此,我
         *	  们重新检查状态,如果停止,则在必要时回滚排队,如果没有,则启动新线程。
         *
         * 3. 若我们无法将任务排队,那个么我们将尝试添加一个新线程。如果失败,我们知道我们已关
         *    闭或饱和,因此拒绝该任务。
         */
        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);
    }
    

    /*
     * 创建、运行和清理的方法 在workers之后
     */

    /**
     * 检查是否可以根据当前池状态和给定绑定(核心或最大值)添加新工作线程。
     * 如果是,将相应地调整工作人员计数,如果可能,将创建并启动一个新的
     * 工作人员,并将firstTask作为其第一个任务运行。如果池已停止或符合
     * 关闭条件,则此方法返回false。如果线程工厂在被请求时未能创建线程,
     * 它也会返回false。如果线程创建失败,无论是由于线程工厂返回null,
     * 还是由于异常(通常是 thread.start() 时的 OutOfMemoryError),
     * 我们都会完全回滚.
     *
     * 入参:firstTask 新线程应首先运行的任务(如果没有,则为空)。
     *				  工作线程是用初始的第一个任务(在方法execute()中)
     *				  创建的,以在少于corePoolSize线程(在这种情况下,
     *				  我们总是启动一个)或队列已满(在这种情形下,我们必
     *				  须绕过队列)时绕过队列。最初,空闲线程通常是通过
     *				  prestartCoreThread创建的,或者用来替换其他正
     *				  在死亡的工作线程.
     *
     * 入参:core 如果为true,则使用corePoolSize作为绑定,否则使用
     			maximumPoolSize。(此处使用布尔指示符,而不是值,
     * 			以确保在检查其他池状态后读取新值。)
     *
     * 返回:true 如果成功
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 仅在必要时检查队列是否为空.
            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;
                // 否则,由于workerCount更改,CAS失败;重试内部循环
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask); // 看下面的 class Worker,非常秀!
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // 持锁时重新检查.
                    // 在ThreadFactory故障或在获取锁之前关闭时退出.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // 预先检查 t 是否可启动
                            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;
    }

    /**
     * Class Worker 主要维护运行任务的线程的中断控制状态,以及其他次要的簿记。
     * 此类机会性地扩展了AbstractQueuedSynchronizer,以简化获取和释放围绕每
     * 个任务执行的锁。这可以防止旨在唤醒等待任务的工作线程而不是中断正在运行的
     * 任务的中断。我们实现了一个简单的非可重入互斥锁,而不是使用重入锁,因为我们
     * 不希望工作任务在调用setCorePoolSize等池控制方法时能够重新获取锁。此外,
     * 为了抑制中断直到线程实际开始运行任务,我们将锁状态初始化为负值,并在启动时
     * 将其清除(在runWorker中).
     */
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        // ...

        /** 此工作线程正在运行。如果工厂失败,则为 null. */
        final Thread thread;
        /** 要运行的初始任务。可能为 null. */
        Runnable firstTask;

        /**
         * 使用来自ThreadFactory的给定第一个任务和线程创建.
         * 入参:firstTask 第一个任务(如果没有,则为null)
         */
        Worker(Runnable firstTask) {
            setState(-1); // 在 runWorker 之前禁止中断
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** 将主运行循环委托给外部的 runWorker  */
        public void run() {
            runWorker(this); // 往下看,把this传给外部的 runWorker 有多惊艳!
        }
        
        // ...
    }

    /**
     * 主工作程序运行循环。重复从队列中获取任务并执行它们,同时处理许多问题:
     *
     * 1. 我们可以从初始任务开始,在这种情况下,我们不需要获得第一个任务。
     * 否则,只要池在运行,我们就通过 getTask 获取任务。如果返回null,
     * 则由于池状态或配置参数的改变,工作进程退出。其他退出是由外部代码中
     * 的异常抛出导致的,在这种情况下,completedAbruptly 保持不变,
     * 这通常会导致 processWorkerExit 替换此线程.
     *
     * 2. 在运行任何任务之前,获取锁以防止任务执行时其他池中断,然后我们
     * 确保除非池停止,否则该线程不会设置其中断.
     *
     * 3. 每个任务运行之前都会调用 beforeExecute,这可能会引发异常,
     * 在这种情况下,我们会导致线程在不处理任务的情况下死亡
     * (completedAbruptly=true 时中断循环).
     *
     * 4. 假设 beforeExecute 正常完成,我们运行任务,收集任何抛出的
     * 异常以发送给 afterExecute。我们分别处理 RuntimeException、
     * Error(这两个都是规范保证我们捕获的)和任意 Throwables。因为
     * 我们不能在 Runnable.run() 时重新抛出 Throwables,我们将它们
     * 包装在输出的 Errors 中(到线程的 UncaughtExceptionHandler)
     * 任何抛出的异常也会保守地导致线程死亡.
     *
     * 5. 任务完成后。运行完成后,我们调用 afterExecute,这也可能引发
     * 异常,这也会导致线程死亡。根据JLS第14.20节的规定,即使是
     * task.run() 抛出的.
     *
     * 异常机制的净效果是,afterExecute 和线程的 
     * UncaughtExceptionHandler 具有尽可能准确的信息,可以提供用户代码
     * 遇到的任何问题.
     *
     * 入参:w 工人
     */
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 允许中断
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // 如果池正在停止,请确保线程已中断;
                // 如果没有,请确保线程未中断。
                // 这需要在第二种情况下进行重新检查,
                // 以在清除中断时处理 shutdownNow 竞赛
                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);
        }
    }
}

2、分析一波

在这里插入图片描述

3、流程

在这里插入图片描述

6、CODE(暂时仅测试3个)

package mii.thread.demo17线程池;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池
 */
public class 线程池 {

    public static void main(String[] args) {
        // 1、创建服务,创建线程池
        ExecutorService pool = Executors.newCachedThreadPool(); // 快
        //pool = Executors.newFixedThreadPool(10); // 慢
        //pool = Executors.newSingleThreadExecutor(); // 非常慢

        for (int i = 0; i < 1000; i++) {
            // 2、将线程丢进池子执行
            pool.execute(new MiThread(i));
        }

        // 3、关闭线程池
        pool.shutdown();
    }

}

class MiThread implements Runnable{
    private int i;

    public MiThread(int i){
        this.i = i;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000); // 模拟业务耗时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---" + i);
    }
}

7、Result+分析

1、newCachedThreadPool 可缓存线程池

1、Result

在这里插入图片描述

2、分析
1、newCachedThreadPool
/**
 * 创建一个线程池,根据需要创建新线程,但在以前构建的线程可用时将重用它们。
 * 这些池通常会提高执行许多短期异步任务的程序的性能。
 * 调用execute将重用以前构造的线程(如果可用)。
 * 如果没有可用的现有线程,将创建一个新线程并将其添加到池中。
 * 60秒内未使用的线程将终止并从缓存中删除。
 * 因此,空闲足够长时间的池不会消耗任何资源。
 * 请注意,可以使用ThreadPoolExecutor构造函数创建具有类似属性但具有不同细节
 * {例如 ThreadPoolExecutor}的池。
 *
 * 返回:新创建的线程池
 */
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(
        0, // corePoolSize:核心线程数
        Integer.MAX_VALUE, // maximumPoolSize:最大线程数
        60L, // keepAliveTime:存活时间
        TimeUnit.SECONDS, // timeUnit:时间单位
        new SynchronousQueue<Runnable>()); // workQueue:阻塞队列
}
2、图解

在这里插入图片描述

这里会涉及到 线程复用

跑了1000个任务,我这台机器跑的时候最多才创建了400多个线程,是因为非核心线程有一个 60s 的存活期限,虽然你手里活干完了,开始计时 60s,只要计时没结束来新活了你就接着干!妥妥的压榨啊!

2、newFixedThreadPool 固定个数线程池

1、Result

在这里插入图片描述

2、分析
1、newFixedThreadPool
/**
 * 创建一个线程池,该线程池可重用在共享无边界队列上运行的固定数量的线程。
 * 在任何时候,最多第n个线程将是活动的处理任务。
 * 如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到有一个线程可用。
 * 如果任何线程在关机前的执行过程中由于故障而终止,则如果需要执行后续任务,将替换新线程。
 * 池中的线程将一直存在,直到显式关闭为止。
 * 
 * 参数:nThreads 池中的线程数
 * 返回:新创建的线程池
 * 抛出:IllegalArgumentException 如果nThreads<=0
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(
    	nThreads, // corePoolSize:核心线程数
    	nThreads, // maximumPoolSize:最大线程数
    	0L, // keepAliveTime:存活时间
    	TimeUnit.MILLISECONDS, // timeUnit:时间单位
    	new LinkedBlockingQueue<Runnable>()); // workQueue:阻塞队列
}
2、图解

在这里插入图片描述

这就是为什么控制台是10个10个的输出了

3、newSingleThreadExecutor 单线程池

1、Result

在这里插入图片描述

2、分析
1、newSingleThreadExecutor
/**
 * 创建一个执行器,该执行器使用单个工作线程在无界队列上运行。
 * (但是,请注意,如果此单个线程在关机前的执行过程中由于故障而终止,
 *   则在需要执行后续任务时,新的线程将取代它。)
 * 任务保证按顺序执行,并且在任何给定时间都不会有多个任务处于活动状态。
 * 与其他等效的 newFixedThreadPool(1) 不同,
 * 返回的执行器保证不可重新配置以使用其他线程。
 *
 * 返回:新创建的单线程执行器
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService(
        new ThreadPoolExecutor(
            1, // corePoolSize:核心线程数
            1, // maximumPoolSize:最大线程数
            0L, // keepAliveTime:存活时间
            TimeUnit.MILLISECONDS, // timeUnit:时间单位
            new LinkedBlockingQueue<Runnable>())); // workQueue:阻塞队列
}
2、图解

在这里插入图片描述

这就是为什么控制台打印的线程名永远是 thread-1


6、对比

  1. newCachedThreadPool
    1. 基本不会造成内存溢出
    2. 只会在创建巨量非核心线程时占满 CPU 更容易导致项目卡死(最大创建21.4亿个,CPU表示:非常淦!)
  2. newFixedThreadPool
    1. 如果队列里排队的任务过多,超过内存大小,就会造成内存溢出
  3. newSingleThreadExecutor
    1. 同newFixedThreadPool,更容易造成内存溢出
  4. 由于给定的线程池可操作性约等于0 ,所以一般都是用自己创建线程池,方便调优等

8、自定义线程池

1、CODE

package mii.thread.demo17线程池;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 自定义线程池
 */
public class 自定义线程池 {

    public static void main(String[] args) {
        ThreadPoolExecutor myPool = new ThreadPoolExecutor(
                10, // corePoolSize:核心线程数
                20, // maximumPoolSize:最大线程数
                0L, // keepAliveTime:存活时间
                TimeUnit.MILLISECONDS, // timeUnit:时间单位
                new ArrayBlockingQueue<Runnable>(10) // workQueue:阻塞队列
        );

        for (int i = 0; i < 1000; i++) {
            // 2、将线程丢进池子执行
            myPool.execute(new MiThread(i));
        }

        // 3、关闭线程池
        myPool.shutdown();
    }

}

class MiniThread implements Runnable{
    private int i;

    public MiniThread(int i){
        this.i = i;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "---" + i);
    }
}

2、Result

在这里插入图片描述

3、分析一波

默认的拒绝策略是 AbortPolicy

核心线程能分摊10个任务,队列里扔10个,非核心线程可分摊10个,等第31个任务进来的时候,没有空位了,直接拒绝!

< 小拓展 >

提交优先级:

  • 核心线程优先级最高
  • 队列次之
  • 非核心线程最低

执行优先级:

  • 核心线程优先级最高
  • 非核心线程次之
  • 队列最低

9、拒绝策略 RejectedExecutionHandler

1、类图

4个拒绝策略实现都是定义在 ThreadPoolExecutor 里的内部类

在这里插入图片描述

2、CODE

public class ThreadPoolExecutor extends AbstractExecutorService {
    /**
     * 默认的拒绝执行处理程序
     */
    private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

    /* 预定义的 RejectedExecutionHandler */

    /**
     * 被拒绝任务的处理程序,直接在{@code execute}方法的调用线程中运行被拒绝的
     * 任务,除非执行器已关闭,在这种情况下,任务将被丢弃。
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * 在调用者的线程中执行任务r,除非执行器已关闭,在这种情况下,任务将被丢弃。
         *
         * 入参:r 请求执行的可运行任务
         * 入参:e 试图执行此任务的执行者
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

    /**
     * 被拒绝任务的处理程序会抛出 {@code RejectedExecutionException} 异常
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * 总是抛出 RejectedExecutionException.
         *
         * 入参:r 请求执行的可运行任务
         * 入参:e 试图执行此任务的执行器
         * 抛出:RejectedExecutionException 总是抛出
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

    /**
     * 被拒绝任务的处理程序,该处理程序静默地丢弃被拒绝的任务.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * 不执行任何操作,这会导致丢弃任务r.
         *
         * 入参:r 请求执行的可运行任务
         * 入参:e 试图执行此任务的执行者
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

    /**
     * 被拒绝任务的处理程序,丢弃最早的未处理请求,然后重试{@code execute},
     * 除非执行器关闭,在这种情况下,任务被丢弃.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * 获取并忽略执行器将执行的下一个任务(如果一个任务立即可用),然后重试执行任务r,
         * 除非执行器关闭,在这种情况下,任务r将被丢弃。
         *
         * 入参:r 请求执行的可运行任务
         * 入参:e 试图执行此任务的执行者
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
}

我们也可以自行拓展,自己实现 RejectedExecutionHandler 接口实现自定义的 rejectedExecution 方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值