💪🏻 制定明确可量化的目标,坚持默默的做事。
一、继承实现关系图
线程池的四种拒绝策略实现关系图:
- AbortPolicy(默认):直接抛出RejectedExecutionException异常并且阻止主线程正常运行
- DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
- CallerRunsPolicy:“调用者运行“一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,让调用者去处理,比如main调用了线程池,线程池处理不了就让main慢慢处理去,从而降低新任务的流量
二、拒绝策略测试示例
// 测试任务类 class Task implements Runnable { private String name; public Task(String name) { this.name = name; } @Override public void run() { try { Thread.sleep(1100); System.out.println("thread name: " + this.name); } catch (Exception e) { e.printStackTrace(); } } }
2.1 AbortPolicy策略
测试代码块:
// ThreadPoolExcecutor类中 private static final RejectedExecutionHandler defaultHandler = new AbortPolicy(); // 测试类 private void abortPolicy() { ThreadFactory factory = r -> new Thread(r, "AbortPolicy ThreadFactory"); // 创建核心线程和最大线程为2的线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), factory); int i = 0; int n = 100; // 添加100个任务 while (i++ < n) { threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i)); } }
运行如上代码打印如下:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task w.cx.lrn.java.interview.threadpool.ThreadPolicyTester$Task@4d405ef7 rejected from java.util.concurrent.ThreadPoolExecutor@6193b845[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0] at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065) at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833) at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365) at w.cx.lrn.java.interview.threadpool.ThreadPolicyTester.abortPolicy(ThreadPolicyTester.java:34) at w.cx.lrn.java.interview.threadpool.ThreadPolicyTester.main(ThreadPolicyTester.java:18) thread name: 1 thread name: 2 thread name: 3 thread name: 4
说明:
- 没有指定拒绝策略,默认为AbortPolicy拒绝策略
- 最大线程数为2,则有两个任务在运行
- 阻塞队列数为2,则阻塞队列中可添加2个任务
- 添加第五个任务的时候抛出了 RejectedExecutionException异常并且阻止主线程正常运行(没有再尝试添加第六个线程),所以只打印前4个任务1,2,3和4任务
2.2 DiscardPolicy策略
测试代码块:(添了拒绝策略)
private void discardPolicy() { ThreadFactory factory2 = r -> new Thread(r, "DiscardPolicy ThreadFactory"); // 核心线程数和、最大线程数 和 阻塞队列大小均为2 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), factory2, new ThreadPoolExecutor.DiscardPolicy()); int i = 0; int n = 10; // 添加10个任务 while (i++ < n) { threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i)); System.out.println("添加了第 " + i + " 个任务!"); } }
运行如上代码打印如下:
添加了第 1 个任务! 添加了第 2 个任务! 添加了第 3 个任务! 添加了第 4 个任务! 添加了第 5 个任务! 添加了第 6 个任务! 添加了第 7 个任务! 添加了第 8 个任务! 添加了第 9 个任务! 添加了第 10 个任务! thread name: 1 thread name: 2 thread name: 4 thread name: 3
说明:
- 指定拒绝策略为DiscardPolicy
- 最大线程数为2,则有两个任务在运行
- 阻塞队列数为2,则阻塞队列中可添加2个任务
- 添加第5个到第10个任务都不予任何处理也不抛出异常,所以只打印前4个任务1,2,3和4任务
2.3 DiscardOldestPolicy策略
测试代码块:(添了DiscardOldestPolicy拒绝策略)
private void discardOldestPolicy() { ThreadFactory factory2 = r -> new Thread(r, "DiscardOldestPolicy ThreadFactory"); // 核心线程数、最大线程数 和 阻塞队列大小均为2 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), factory2, new ThreadPoolExecutor.DiscardOldestPolicy()); int i = 0; int n = 10; // 添加10个任务 while (i++ < n) { threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i)); System.out.println("添加了第 " + i + " 个任务!"); } }
运行如上代码打印如下:
添加了第 1 个任务! 添加了第 2 个任务! 添加了第 3 个任务! 添加了第 4 个任务! 添加了第 5 个任务! 添加了第 6 个任务! 添加了第 7 个任务! 添加了第 8 个任务! 添加了第 9 个任务! 添加了第 10 个任务! thread name: 1 thread name: 2 thread name: 9 thread name: 10
说明:
- 指定拒绝策略为DiscardOldestPolicy
- 最大线程数为2,则有两个任务在运行
- 阻塞队列数为2,则阻塞队列中可添加2个任务
- 拒绝策略为DiscardOldestPolicy,添加第5个时,则抛弃第3个任务添加第5个任务,添加第6个任务时,抛弃第4个任务添加第6个任务,依次类推最后阻塞队列中存放的任务为第9个和第10个,所以最后只打印前2个任务1,2和9、10任务
2.4 CallerRunsPolicy策略
测试代码块:(添了CallerRunsPolicy拒绝策略)
private void callerRunsPolicy() { ThreadFactory factory2 = r -> new Thread(r, "TestThreadPool"); // 核心线程数、最大线程数 和 阻塞队列大小均为2 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), factory2, new ThreadPoolExecutor.CallerRunsPolicy()); int i = 0; int n = 10; // 添加10个任务 while (i++ < n) { threadPoolExecutor.execute(new ThreadPolicyTester.Task("" + i)); System.out.println("添加了第 " + i + " 个任务!"); } }
为了方便辨别是主线程执行还是子线程执行,测试任务类的打印修改成如下:
System.out.println("thread name: " + Thread.currentThread().getName() + " " + this.name);
运行如上代码打印如下:
添加了第 1 个任务! 添加了第 2 个任务! 添加了第 3 个任务! 添加了第 4 个任务! thread name: callerRunsPolicy 2 thread name: callerRunsPolicy 1 thread name: main 5 添加了第 5 个任务! 添加了第 6 个任务! 添加了第 7 个任务! thread name: callerRunsPolicy 3 thread name: main 8 添加了第 8 个任务! 添加了第 9 个任务! thread name: callerRunsPolicy 4 thread name: callerRunsPolicy 6 thread name: main 10 thread name: callerRunsPolicy 7 添加了第 10 个任务! thread name: callerRunsPolicy 9
说明:(多线程环境下运行,每次打印都不一样)
- 指定拒绝策略为CallerRunsPolicy
- 最大线程数为2,则有两个任务在运行
- 阻塞队列数为2,则阻塞队列中可添加2个任务
- 10任务都打印出来了,被拒绝的第5、8 和 10任务是由主线程执行的,拒绝策略源码如下。就是说添加新任务被拒绝后,只要主线程创建的线程池没有关闭,由主线程来执行被拒绝的新任务。只有线程池关闭了,被拒绝的任务才被丢弃且不抛异常。
拒绝策略源码:
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } }
三、总结及应用场景
- AbortPolicy拒绝策略:新任务不能再添加的时候,会直接抛出异常及时反馈程序运行状态,在一些比较重要的业务中,使用此策略在不能再处理更多的并发量的时候,就能及时的通过异常发现问题。
- DiscardPolicy拒绝策略:在不能再添加新任务时此策略直接丢弃任务。比如在一些无关紧要的任务可用此策略。
- DiscardOldestPolicy拒绝策略:新任务替换老任务。应用于任务分配有一定的容忍性的情况,弃用最老的任务以容纳新的任务,一般是可以接受的情况下。
- CallerRunsPolicy拒绝策略:只要主线程创建的线程池没有关闭,则由主线程来执行任务。这种由主线程来一个一个执行被拒绝新任务,从而实现降低新任务的提交速度。