Java并发(五)线程池使用番外-分析RejectedExecutionException异常
目录
一、入门示例
二、异常场景1
三、异常场景2
四、解决方法
之前在使用线程池的时候,出现了 java.util.concurrent.RejectedExecutionException ,原因是线程池配置不合理,导致提交的任务来不及处理。接下来用一个简单的例子来复现异常。
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.cellphone.common.pool.Worker@f6f4d33 rejected from java.util.concurrent.ThreadPoolExecutor@23fc625e[Running, pool size = 3, active threads = 3, queued tasks = 15, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at org.cellphone.common.pool.RejectedExecutionExceptionExample.main(RejectedExecutionExceptionExample.java:22)
一、入门示例
下面的测试程序使用 ThreadPoolExecutor 类来创建线程池执行任务,代表任务 Worker 类代码如下:
/**
* Created by on 2019/4/20.
*/
public class Worker implements Runnable { private int id; public Worker(int id) {
this.id = id;
} @Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 执行任务 " + id);
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 完成任务 " + id);
} catch (Exception e) {
e.printStackTrace();
}
}
}
执行 Worker 任务的代码如下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
* Created by on 2019/4/20.
*/
public class RejectedExecutionExceptionExample { public static void main(String[] args) { ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); Worker tasks[] = new Worker[10];
for (int i = 0; i < 10; i++) {
tasks[i] = new Worker(i);
System.out.println("提交任务: " + tasks[i] + ", " + i);
executor.execute(tasks[i]);
}
System.out.println("主线程结束");
executor.shutdown(); // 关闭线程池
}
}
运行一下,看到如下输出:
提交任务: org.cellphone.common.pool.Worker@36baf30c, 0
提交任务: org.cellphone.common.pool.Worker@5ca881b5, 1
提交任务: org.cellphone.common.pool.Worker@4517d9a3, 2
提交任务: org.cellphone.common.pool.Worker@2f92e0f4, 3
提交任务: org.cellphone.common.pool.Worker@28a418fc, 4
提交任务: org.cellphone.common.pool.Worker@5305068a, 5
提交任务: org.cellphone.common.pool.Worker@1f32e575, 6
提交任务: org.cellphone.common.pool.Worker@279f2327, 7
提交任务: org.cellphone.common.pool.Worker@2ff4acd0, 8
提交任务: org.cellphone.common.pool.Worker@54bedef2, 9
主线程结束
pool-1-thread-1 执行任务 0
pool-1-thread-2 执行任务 1
pool-1-thread-3 执行任务 2
pool-1-thread-1 完成任务 0
pool-1-thread-1 执行任务 3
pool-1-thread-2 完成任务 1
pool-1-thread-2 执行任务 4
pool-1-thread-3 完成任务 2
pool-1-thread-3 执行任务 5
pool-1-thread-2 完成任务 4
pool-1-thread-2 执行任务 6
pool-1-thread-3 完成任务 5
pool-1-thread-1 完成任务 3
pool-1-thread-3 执行任务 7
pool-1-thread-1 执行任务 8
pool-1-thread-3 完成任务 7
pool-1-thread-2 完成任务 6
pool-1-thread-1 完成任务 8
pool-1-thread-2 执行任务 9
pool-1-thread-2 完成任务 9
在 RejectedExecutionExceptionExample 类里,我们使用 ThreadPoolExecutor 类创建了一个数量为3的线程池来执行任务,在这3个线程执行任务被占用期间,如果有新任务提交给线程池,那么这些新任务会被保存在 BlockingQueue 阻塞队列里,以等待被空闲线程取出并执行。在这里我们使用一个大小为15的 ArrayBlockingQueue 队列来保存待执行的任务,然后我们创建了10个任务提交给 ThreadPoolExecutor 线程池。
二、异常场景1
产生 RejectedExecutionException 异常的第一个原因:
调用 shutdown() 方法关闭了 ThreadPoolExecutor 线程池,又提交新任务给 ThreadPoolExecutor 线程池执行。一般调用 shutdown() 方法之后,JVM会得到一个关闭线程池的信号,并不会立即关闭线程池,原来线程池里未执行完的任务仍然在执行,等到任务都执行完后才关闭线程池,但是JVM不允许再提交新任务给线程池。
让我们用以下例子来重现该异常:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
* Created by on 2019/4/20.
*/
public class RejectedExecutionExceptionExample { public static void main(String[] args) { ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); Worker tasks[] = new Worker[10];
for (int i = 0; i < 10; i++) {
tasks[i] = new Worker(i);
System.out.println("提交任务: " + tasks[i] + ", " + i);
executor.execute(tasks[i]);
}
System.out.println("主线程结束");
executor.shutdown();// 关闭线程池
executor.execute(tasks[0]);// 关闭线程池之后提交新任务,运行之后抛异常
}
}
运行一下,看到如下输出:
提交任务: org.cellphone.common.pool.Worker@36baf30c, 0
提交任务: org.cellphone.common.pool.Worker@5ca881b5, 1
提交任务: org.cellphone.common.pool.Worker@4517d9a3, 2
提交任务: org.cellphone.common.pool.Worker@2f92e0f4, 3
提交任务: org.cellphone.common.pool.Worker@28a418fc, 4
提交任务: org.cellphone.common.pool.Worker@5305068a, 5
提交任务: org.cellphone.common.pool.Worker@1f32e575, 6
提交任务: org.cellphone.common.pool.Worker@279f2327, 7
提交任务: org.cellphone.common.pool.Worker@2ff4acd0, 8
提交任务: org.cellphone.common.pool.Worker@54bedef2, 9
主线程结束
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task org.cellphone.common.pool.Worker@36baf30c rejected from java.util.concurrent.ThreadPoolExecutor@5caf905d[Shutting down, pool size = 3, active threads = 3, queued tasks = 7, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at org.cellphone.common.pool.RejectedExecutionExceptionExample.main(RejectedExecutionExceptionExample.java:26)
pool-1-thread-1 执行任务 0
pool-1-thread-2 执行任务 1
pool-1-thread-3 执行任务 2
pool-1-thread-1 完成任务 0
pool-1-thread-1 执行任务 3
pool-1-thread-2 完成任务 1
pool-1-thread-2 执行任务 4
pool-1-thread-3 完成任务 2
pool-1-thread-3 执行任务 5
pool-1-thread-3 完成任务 5
pool-1-thread-3 执行任务 6
pool-1-thread-2 完成任务 4
pool-1-thread-2 执行任务 7
pool-1-thread-1 完成任务 3
pool-1-thread-1 执行任务 8
pool-1-thread-3 完成任务 6
pool-1-thread-2 完成任务 7
pool-1-thread-3 执行任务 9
pool-1-thread-1 完成任务 8
pool-1-thread-3 完成任务 9
从以上例子可以看出,在调用 shutdown() 方法之后,由于JVM不允许再提交新任务给线程池,于是抛出了 RejectedExecutionException 异常。
三、异常场景2
产生 RejectedExecutionException 异常第二个原因:
要提交给阻塞队列的任务超出了该队列的最大容量。当线程池里的线程都繁忙的时候,新任务会被提交给阻塞队列保存,这个阻塞队列一旦饱和,线程池就会拒绝接收新任务,随即抛出异常。
示例代码如下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
* Created by on 2019/4/20.
*/
public class RejectedExecutionExceptionExample { public static void main(String[] args) { ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); // 提交20个任务给线程池
Worker tasks[] = new Worker[20];
for (int i = 0; i < 20; i++) {
tasks[i] = new Worker(i);
System.out.println("提交任务: " + tasks[i] + ", " + i);
executor.execute(tasks[i]);
}
System.out.println("主线程结束");
executor.shutdown();// 关闭线程池
}
}
在上面的例子中,我们使用了一个大小为15的 ArrayBlockingQueue 阻塞队列来保存等待执行的任务。接着我们提交了20个任务给线程池,由于每个线程执行任务的时候会睡眠1秒,因此当3个线程繁忙的时候,其他任务不会立即得到执行,我们提交的新任务会被保存在队列里。当等待任务的数量超过线程池阻塞队列的最大容量时,抛出了 RejectedExecutionException 异常。
四、解决方法
要解决 RejectedExecutionException 异常,首先我们要注意两种情况:
- 当调用了线程池的
shutdown()方法以后,不要提交新任务给线程池 - 不要提交大量超过线程池处理能力的任务,这时可能会导致队列饱和,抛出异常
对于第二种情况,我们很容易解决。我们可以选择一种不需要设置大小限制的数据结构,比如 LinkedBlockingQueue 阻塞队列。因此在使用 LinkedBlockingQueue 队列以后,如果还出现 RejectedExecutionException 异常,就要将问题的重点放在第一种情况上。如果第一种情况不是产生问题的原因,那么我们还需要寻找更复杂的原因。比如,由于线程死锁和 LinkedBlockingQueue 饱和,导致内存占用过大,这个时候我们就需要考虑JVM可用内存的问题了。
对于第二种情况,通常有一些隐藏的信息被我们忽略。其实我们可以给使用 ArrayBlockingQueue 作为阻塞队列的 ThreadPoolExecutor 线程池提交超过15个的任务,只要我们在提交新任务前设置一个完成原来任务的等待时间,这时3个线程就会逐渐消费 ArrayBlockingQueue 阻塞队列里的任务,而不会使它堵塞。示例如下:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; /**
* Created by on 2019/4/20.
*/
public class RejectedExecutionExceptionExample { public static void main(String[] args) throws InterruptedException { ExecutorService executor = new ThreadPoolExecutor(3, 3, 0L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(15)); // 提交20个任务给线程池
Worker tasks[] = new Worker[20];
for (int i = 0; i < 10; i++) {
tasks[i] = new Worker(i);
System.out.println("提交任务: " + tasks[i] + ", " + i);
executor.execute(tasks[i]);
} Thread.sleep(3000);// 让主线程睡眠三秒
for (int i = 10; i < 20; i++) {
tasks[i] = new Worker(i);
System.out.println("提交任务: " + tasks[i] + ", " + i);
executor.execute(tasks[i]);
} System.out.println("主线程结束");
executor.shutdown();// 关闭线程池
}
}
运行一下,看到如下输出:
提交任务: org.cellphone.common.pool.Worker@36baf30c, 0
提交任务: org.cellphone.common.pool.Worker@5ca881b5, 1
提交任务: org.cellphone.common.pool.Worker@4517d9a3, 2
提交任务: org.cellphone.common.pool.Worker@2f92e0f4, 3
提交任务: org.cellphone.common.pool.Worker@28a418fc, 4
提交任务: org.cellphone.common.pool.Worker@5305068a, 5
提交任务: org.cellphone.common.pool.Worker@1f32e575, 6
提交任务: org.cellphone.common.pool.Worker@279f2327, 7
提交任务: org.cellphone.common.pool.Worker@2ff4acd0, 8
提交任务: org.cellphone.common.pool.Worker@54bedef2, 9
pool-1-thread-1 执行任务 0
pool-1-thread-2 执行任务 1
pool-1-thread-3 执行任务 2
pool-1-thread-2 完成任务 1
pool-1-thread-3 完成任务 2
pool-1-thread-1 完成任务 0
pool-1-thread-3 执行任务 4
pool-1-thread-2 执行任务 3
pool-1-thread-1 执行任务 5
pool-1-thread-3 完成任务 4
pool-1-thread-3 执行任务 6
pool-1-thread-2 完成任务 3
pool-1-thread-2 执行任务 7
pool-1-thread-1 完成任务 5
pool-1-thread-1 执行任务 8
提交任务: org.cellphone.common.pool.Worker@5caf905d, 10
提交任务: org.cellphone.common.pool.Worker@27716f4, 11
提交任务: org.cellphone.common.pool.Worker@8efb846, 12
提交任务: org.cellphone.common.pool.Worker@2a84aee7, 13
提交任务: org.cellphone.common.pool.Worker@a09ee92, 14
提交任务: org.cellphone.common.pool.Worker@30f39991, 15
提交任务: org.cellphone.common.pool.Worker@452b3a41, 16
提交任务: org.cellphone.common.pool.Worker@4a574795, 17
提交任务: org.cellphone.common.pool.Worker@f6f4d33, 18
pool-1-thread-3 完成任务 6
pool-1-thread-2 完成任务 7
pool-1-thread-1 完成任务 8
pool-1-thread-2 执行任务 10
pool-1-thread-3 执行任务 9
提交任务: org.cellphone.common.pool.Worker@23fc625e, 19
pool-1-thread-1 执行任务 11
主线程结束
pool-1-thread-2 完成任务 10
pool-1-thread-2 执行任务 12
pool-1-thread-1 完成任务 11
pool-1-thread-1 执行任务 13
pool-1-thread-3 完成任务 9
pool-1-thread-3 执行任务 14
pool-1-thread-2 完成任务 12
pool-1-thread-2 执行任务 15
pool-1-thread-3 完成任务 14
pool-1-thread-3 执行任务 16
pool-1-thread-1 完成任务 13
pool-1-thread-1 执行任务 17
pool-1-thread-2 完成任务 15
pool-1-thread-2 执行任务 18
pool-1-thread-3 完成任务 16
pool-1-thread-1 完成任务 17
pool-1-thread-3 执行任务 19
pool-1-thread-2 完成任务 18
pool-1-thread-3 完成任务 19
当然上面这种设置等待时间来分隔旧任务和新任务的方式,在高并发情况下效率并不高,一方面由于我们无法准确预估等待时间,一方面由于 ArrayBlockingQueue 内部只使用了一个锁来隔离读和写的操作,因此效率没有使用了两个锁来隔离读写操作的 LinkedBlockingQueue 高,故而不推荐使用这种方式。
Java并发(五)线程池使用番外-分析RejectedExecutionException异常的更多相关文章
- Java并发编程——线程池的使用
在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...
- Java 并发编程 | 线程池详解
原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...
- Java并发编程--线程池
1.ThreadPoolExecutor类 java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,下面我们来看一下ThreadPoolExecuto ...
- Java并发编程——线程池
本文的目录大纲: 一.Java中的ThreadPoolExecutor类 二.深入剖析线程池实现原理 三.使用示例 四.如何合理配置线程池的大小 一.Java中的ThreadPoolExecutor类 ...
- java并发:线程池、饱和策略、定制、扩展
一.序言 当我们需要使用线程的时候,我们可以新建一个线程,然后显式调用线程的start()方法,这样实现起来非常简便,但在某些场景下存在缺陷:如果需要同时执行多个任务(即并发的线程数量很多),频繁地创 ...
- Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式
前言: 在刚学Java并发的时候基本上第一个demo都会写new Thread来创建线程.但是随着学的深入之后发现基本上都是使用线程池来直接获取线程.那么为什么会有这样的情况发生呢? new Thre ...
- JAVA 并发编程-线程池(七)
线程池的作用: 线程池作用就是限制系统中运行线程的数量. 依据系统的环境情况.能够自己主动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数量,其 ...
- java并发编程-线程池的使用
参考文章:http://www.cnblogs.com/dolphin0520/p/3932921.html 深入剖析线程池实现原理 将从下面几个方面讲解: 1.线程池状态 2.任务的执行 3.线程池 ...
- Java核心复习——线程池ThreadPoolExecutor源码分析
一.线程池的介绍 线程池一种性能优化的重要手段.优化点在于创建线程和销毁线程会带来资源和时间上的消耗,而且线程池可以对线程进行管理,则可以减少这种损耗. 使用线程池的好处如下: 降低资源的消耗 提高响 ...
随机推荐
- Linux下编译、链接和装载
——<程序员的自我修养>读书笔记 编译过程 在Linux下使用GCC将源码编译成可执行文件的过程可以分解为4个步骤,分别是预处理(Prepressing).编译(Compilation). ...
- blfs(systemd版本)学习笔记-配置远程连接显示中文
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 远程连接的lfs系统需要具备以下环境便可在xshell或其他远程终端上面显示中文: 1.lfs主机设置中文编码(需要配置) 2. ...
- Ubuntu 16.04 LTS 下安装 ibus-rime 输入法
搜 Linux 下粤拼输入法的时候发现了 Rime,由于 fcitx 下的拼音输入体验实在不太好(搜狗是在我的电脑上完全坏掉了,调不出来,配置文件的问题一直没解决:谷歌是好过没有),于是安装 ibus ...
- 2018-01-11 Antlr4的分析错误处理
中文编程知乎专栏原文地址 (前文通用型的中文编程语言探讨之一: 高考, 即使是这"第一步", 即使一切顺利达到列出的功能恐怕也需要个人数年的业余时间. 看到不少乎友都远更有资本和实 ...
- 2017-12-22 日语编程语言"抚子"-第三版实现初探
前文日语编程语言"抚子" - 第三版特色初探仅对语言的语法进行了初步了解. 之前的语言原型实现尝试(如编程语言试验之Antlr4+JavaScript实现"圈4" ...
- python之约束, 异常处理, md5
1. 类的约束 1. 写一个父类. 父类中的某个方法要抛出一个异常 NotImplementedError (重点) 2. 抽象类和抽象方法 # 语法 # from abc import ABCMet ...
- [VUE ERROR] Error in render: "TypeError: Cannot create property 'header' on boolean 'true'"
项目基于ElemnetUi进行的开发,在引入第三方扩展库 vue-element-extends 之后使用它的表格组件报了这个错 解决方案: 1. 删除项目中的 node_modules 2. 删除 ...
- linux网络 skb_buff
sbk_buff中的data_len指的是尾部带的page数据的长度,len指的是总共的data的长度,len-data_len是第一个线性buf的数据长度. sk_buff->len:表示当前 ...
- Fiddler抓包使用教程-Https
转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/72956016 本文出自[赵彦军的博客] 开启 Https 抓包 Fiddler 默 ...
- Elasticsearch5.4署遇到的问题
问题一 can not run elasticsearch as root Elastic 不建议通过root用户启动ES服务器,如果非要用root启动,可以在config/jvm.options配置 ...