当线程池的任务缓存队列已满并且线程池中的线程数目达到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 26 |
|
输出:
task-0 is running.
java.util.concurrent.RejectedExecutionException: Task MyRunnable@68de145 rejected from java.util.concurrent.ThreadPoolExecutor@27fa135a[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(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 38 |
|
输出结果:
task-0 is running.
task-1 is running.
结果分析:
线程池的第一个参数决定可以并发几个线程,这里设为1,那么就表示线程只能一个一个来,另一个参数是最后这个ArrayBlockingQueue所代表的,意思是如果新提交到线程池的线程如果当前没有执行额度(例如上面这种一次只能执行1个线程,我这里就暂称为执行额度为1),那么只能放到这个阻塞队列中等待,而阻塞队列本身也是有大小的,所以也会满,满了怎么办,就对应上面说的四种策略,所以上面的代码如果在33行后加一行Thread.sleep(200);则10个线程都能得到执行,因为上面设定了每个线程的执行时间为100毫秒,那么只要等超过100毫秒的时间,线程就会执行完毕并释放资源,则后序提交的线程就来得及进入线程池,否则像上面的代码那样太急着提交,直接就会导致阻塞的发生,从而触发拒绝策略。
3. DiscardOldestPolicy策略:
import
java.util.concurrent.ArrayBlockingQueue;
import
java.util.concurrent.ThreadPoolExecutor;
import
java.util.concurrent.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<Runnable>(CAPACITY));
// 设置线程池的拒绝策略为"DiscardOldestPolicy"
pool.setRejectedExecutionHandler(
new
ThreadPoolExecutor.DiscardOldestPolicy());
// 新建10个任务,并将它们添加到线程池中。
for
(
int
i =
0
; i <
10
; i++) {
Runnable myrun =
new
MyRunnable(
"task-"
+i);
pool.execute(myrun);
}
// 关闭线程池
pool.shutdown(); }
}
输出结果为:
task-0 is running.
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 20 |
|
结果:
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.
结果分析:
首先,所有的人物都得到了执行,没有一个被漏掉或者抛出异常什么的,其次,注意到顺序看起来乱七八糟,但是仔细看还是乱中有序,可以这样理解:相当于现在有三个槽,一个是执行线程空间,一个是阻塞队列,一个是主线程,前两个槽视为一个整体,这个整体跟主线程的槽对比,谁有空,线程就进入谁,最终可以决定执行的顺序。