线程池--拒绝策略RejectedExecutionHandler 原理和实验
当线程池的任务缓存队列已满并且线程池中的线程数⽬达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 默认策略
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前⾯的任务,然后重新尝试执⾏任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调⽤线程处理该任务
下⾯来看⼏个例⼦:
1. AbortPolicy策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26import urrent.ArrayBlockingQueue;
import urrent.RejectedExecutionException;
import urrent.ThreadPoolExecutor;
import urrent.TimeUnit;
public class AbortPolicyDemo {
private static final int THREADS_SIZE = 1;
private static final int CAPACITY = 1;
public static void main(String[] args) throws Exception {
// 创建线程池。线程池的"最⼤池⼤⼩"和"核⼼池⼤⼩"都为1(THREADS_SIZE),"线程池"的阻塞队列容量为1(CAPACITY)。 ThreadPoolExecutor pool = new ThreadPoolExecutor(THREADS_SIZE, THREADS_SIZE, 0, TimeUnit.SECONDS, new Arra yBlockingQueue<Runnable>(CAPACITY));
// 设置线程池的拒绝策略为"抛出异常"
pool.tRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
try{
// 新建10个任务,并将它们添加到线程池中。
for(int i = 0; i < 10; i++) {
Runnable myrun = new MyRunnable("task-"+i);
}
}catch(RejectedExecutionException e) {
e.printStackTrace();
// 关闭线程池
pool.shutdown();
}
}
}
输出:
task-0 is running.
urrent.RejectedExecutionException: Task MyRunnable@68de145 rejected from
urrent.ThreadPoolExecutor@27fa135a[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
at urrent.jectedExecution(ThreadPoolExecutor.java:2063)
at ject(ThreadPoolExecutor.java:830)
at ute(ThreadPoolExecutor.java:1379)
at AbortPolicyDemo.main(AbortPolicyDemo.java:18)
task-1 is running.
结果分析:
先是task0占有了唯⼀的线程执⾏额度并开始执⾏,随后task1占有阻塞队列的唯⼀空间,随后task2想执⾏,发现task0还没跑完,执⾏额度⼜满了,于是尝试进⼊阻塞队列,结果发现阻塞队列被task1给占了,⽆路可⾛的task2于是抛出异常,关闭线程池。
2.DiscardPolicy策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38import urrent.ArrayBlockingQueue;
import urrent.ThreadPoolExecutor;
import urrent.TimeUnit;
class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
try{
System.out.println(this.name + " is running.");
Thread.sleep(100);
} catch(Exception e) {
e.printStackTrace();
}
}
}
public class DiscardPolicyDemo
{
private static final int THREADS_SIZE = 1;
private static final int CAPACITY = 1;
public static void main(String[] args) throws Exception {
// 创建线程池。线程池的"最⼤池⼤⼩"和"核⼼池⼤⼩"都为1(THREADS_SIZE),"线程池"的阻塞队列容量为1(CAPACITY)。 ThreadPoolExecutor pool = new ThreadPoolExecutor(THREADS_SIZE, THREADS_SIZE, 0, TimeUnit.SECONDS, new Arra yBlockingQueue<>(CAPACITY));
// 设置线程池的拒绝策略为"丢弃"
pool.tRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
// 新建10个任务,并将它们添加到线程池中。
for(int i = 0; i < 10; i++) {
Runnable myrun = new MyRunnable("task-"+i);
}
// 关闭线程池
pool.shutdown();
}
}
task-1 is running.
结果分析:
线程池的第⼀个参数决定可以并发⼏个线程,这⾥设为1,那么就表⽰线程只能⼀个⼀个来,另⼀个参数是最后这个ArrayBlockingQueue
所代表的,意思是如果新提交到线程池的线程如果当前没有执⾏额度(例如上⾯这种⼀次只能执⾏1个线程,我这⾥就暂称为执⾏额度为
1),那么只能放到这个阻塞队列中等待,⽽阻塞队列本⾝也是有⼤⼩的,所以也会满,满了怎么办,就对应上⾯说的四种策略,所以上⾯
的代码如果在33⾏后加⼀⾏Thread.sleep(200);则10个线程都能得到执⾏,因为上⾯设定了每个线程的执⾏时间为100毫秒,那么只要等
超过100毫秒的时间,线程就会执⾏完毕并释放资源,则后序提交的线程就来得及进⼊线程池,否则像上⾯的代码那样太急着提交,直接就
会导致阻塞的发⽣,从⽽触发拒绝策略。
3. DiscardOldestPolicy策略:
import urrent.ArrayBlockingQueue;
import urrent.ThreadPoolExecutor;
import urrent.TimeUnit;
public class DiscardOldestPolicyDemo
{
private static final int THREADS_SIZE = 1;
private static final int CAPACITY = 1;
public static void main(String[] args) throws Exception {
// 创建线程池。线程池的"最⼤池⼤⼩"和"核⼼池⼤⼩"都为1(THREADS_SIZE),"线程池"的阻塞队列容量为1(CAPACITY)。
ThreadPoolExecutor pool = new ThreadPoolExecutor(THREADS_SIZE, THREADS_SIZE, 0, TimeUnit.SECONDS, new ArrayBlockingQueue
// 设置线程池的拒绝策略为"DiscardOldestPolicy"
pool.tRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
// 新建10个任务,并将它们添加到线程池中。
for(int i = 0; i < 10; i++) {
Runnable myrun = new MyRunnable("task-"+i);
}
// 关闭线程池
pool.shutdown(); }
}
task-9 is running.
结果分析:
可见1到8都被挤掉了,推演如下:先task0进⼊开始执⾏200ms,在这200ms内,发⽣了如下后序事件:task1进⼊阻塞队列等
待,task2想进⼊阻塞队列,但是发现task1占着茅坑,于是把它挤出去,取⽽代之,task3想进⼊阻塞队列,⼜发现task2占着茅坑,于是把它挤出去,取⽽代之,以此类推,最终task-9占据了阻塞队列的位置,并等task0完成后task9开始执⾏。于是乎有了上⾯的输出。
4. CallerRunsPolicy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20import urrent.ArrayBlockingQueue;
import urrent.ThreadPoolExecutor;
import urrent.TimeUnit;
public class CallerRunsPolicyDemo {
private static final int THREADS_SIZE = 1;
private static final int CAPACITY = 1;
public static void main(String[] args) throws Exception {
// 创建线程池。线程池的"最⼤池⼤⼩"和"核⼼池⼤⼩"都为1(THREADS_SIZE),"线程池"的阻塞队列容量为1(CAPACITY)。 ThreadPoolExecutor pool = new ThreadPoolExecutor(THREADS_SIZE, THREADS_SIZE, 0, TimeUnit.SECONDS, new Arra yBlockingQueue<>(CAPACITY));
// 设置线程池的拒绝策略为"CallerRunsPolicy"
pool.tRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 新建10个任务,并将它们添加到线程池中。
for(int i = 0; i < 10; i++) {
Runnable myrun = new MyRunnable("task-"+i); ute(myrun);
}
// 关闭线程池
pool.shutdown();
}
}
结果:
task-2 is running. task-0 is running. task-3 is running. task-1 is running. task-5 is running. task-4 is running. task-7 is running. task-6 is running. task-9 is running.
task-8 is running.
结果分析:
⾸先,所有的⼈物都得到了执⾏,没有⼀个被漏掉或者抛出异常什么的,其次,注意到顺序看起来乱七⼋糟,但是仔细看还是乱中有序,可以这样理解:相当于现在有三个槽,⼀个是执⾏线程空间,⼀个是阻塞队列,⼀个是主线程,前两个槽视为⼀个整体,这个整体跟主线程的槽对⽐,谁有空,线程就进⼊谁,最终可以决定执⾏的顺序。
除了以上4种java⾃带的策略外,还可以⾃定义策略,例如:
1 2 3 4 5 6private final RejectedExecutionHandler mHandler = new RejectedExecutionHandler() { @Override
public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {
mTaskQueue.offer(task);
}
};
我这⾥⾃定义了⼀个策略,只需要实现这个RejectedExecutionHandler interface ,具体就是要实现其⽅法rejectedExecution, 然后只要线程池设置tRejectedExecutionHandler的时候使⽤这个mHandler就⾏。