前段时间,夜晚突然收到报警,紧急上线排查。由于dba操作不当,大片数据回滚,发生锁表的情况,请求返回时间过长,使得系统打印出大量的RejectedExecutionException的异常。定位到代码片段类似:
ThreadPoolExecutor workers = new ThreadPoolExecutor(10, 600, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
ShareTask shareTask = new ShareTask(shareInfo, clientId, rtnResult,
outerShareContext, importOuterFriend, bindingManager,shareUserManager);
workers.execute(shareTask);
这里就要说说ThreadPoolExecutor和ArrayBlockingQueue了,众所周知ArrayBlockingQueue类是一个阻塞的队列。当和ThreadPoolExecutor使用时,ThreadPoolExecutor会在初始化时开启corePoolSize(也就是上面代码中的10)个线程,去消费队列里的task。当并发量增大,直到corePoolSize全都在执行task,ThreadPoolExecutor不会立即新建消费线程,而是将新加入的task放入到对队列中,一直到放满,ThreadPoolExecutor才会去创建一个新的线程。直至达到线程池中的消费线程达到maximumPoolSize(也就是上面代码里的600)。如果这个时候再有task加入,根据默认的饱和策略,将会抛出RejectedExecutionException异常。
这次就是由于获取数据库等待时间超长,导致task响应时间变慢,继而线程池中活跃的消费线程堆积到600个线程依然无法应付,才抛出的RejectedExecutionException。
显然抛出RejectedExecutionException不是那么的友好,我们在这里可以自定义饱和策略。默认系统饱和策略是抛出异常。
workers.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
}
});
另外当线程故障恢复时,通过日志惊奇的观察到,队列中的线程以串行的方式运行了一短时间,不过很快就正常了。于是在本地写了一个测试。
ThreadPoolExecutor workers = new ThreadPoolExecutor(10, 100, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10));
for (int i = 0; i < 110; i++) {
workers.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
throw new RuntimeException();
}
});
}
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
System.out.println(String.format("task count:%s; active count:%s", workers.getTaskCount(), workers.getActiveCount()));
}
运行如上的代码,打印日志如下:
task count:110; active count:100
task count:10; active count:1
task count:10; active count:1
task count:9; active count:1
task count:9; active count:1
...
task count:2; active count:1
task count:2; active count:1
task count:1; active count:1
task count:1; active count:1
可以看到,最后的10个task是以类似串行的方式在运行。
这里的原因在于ThreadPoolExecutor的中断策略,当Runnable中抛出RTE时,ThreadPoolExecutor会将执行当前的Runnable的线程Dead。由于例子的代码100%会抛出RTE,最终的结果就是ThreadPoolExecutor中存活的消费线程数变为0。ThreadPoolExecutor创建线程只有在初始化,调用excute等几个主动方法中才会去做,我们任务的提交早在一开始就已经做了,最终导致的结果就是永远只存在一个线程服务这个任务队列。
那么要比较优雅的解决这个问题,可以使用FutureTask,FutureTask里面会处理运行时异常,不会将其抛出给ThreadPoolExecutor。
分享到:
相关推荐
android handler runnable使用实例(关键是内部run中停止)
Java中Runnable和Thread的区别
主要介绍了详解Java中多线程异常捕获Runnable的实现的相关资料,希望通过本文能帮助到大家,让大家理解掌握这样的知识,需要的朋友可以参考下
在Java中创建线程有两种方法:使用Thread类和使用Runnable接口。在使用Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。
java多线程runnable实例,经过测试的,可以直接运行
Autosar SWC在Simulink中设置多个runnable的测试模型及生成的代码及Arxml文件
在一个线程中求100以内的偶数,求出一个偶数后休眠一个随机时间在(1-300毫秒之间).在另一个线程中求奇数,求出一个奇数后也休眠一个随机时间(1-300毫秒之间).输出数据时应有提示,指明是哪个线程输出的数据 用实例...
主要介绍了Java中的Runnable,Callable,Future,FutureTask的比较的相关资料,需要的朋友可以参考下
在Java中只支持单继承,因此通过继承Thread类创建线程有一定的局限性,这时可以使用另一种方式,即实现Runnable接口来创建线程。通过这种方式需要在Thread(Runnable target)的构造方法中,传递一个实现了Runnable...
thread 线程类 实现runnable接口
1. 什么是Runnable接口: 1.1 介绍Runnable接口 1.2 与Thread类的对比 2. 创建线程的方式: 2.1 继承Thread类 2.2 实现Runnable接口 3. 实现Runnable接口: 3.1 实现步骤 3.2 优点与用途 4. 启动线程: 4.1 使用...
大家都知道Runnable和Callable接口都可以作为其他线程执行的任务,但是Runnable接口的run方法没有返回值,而Callable接口的call方法有返回值,那么Callable接口是如何做到的呢?在此我给出一个Demo,看看通过...
声明自定义线程类实现Runnable接口实现奇数/偶数序列线程并发执行。
EB_Tresos导入System desk中新增Task(runnable).,让Autosar配置不在高不可攀。减少汽车电子开发过程中的拦路虎。
Java 小游戏 键盘打字练习! 可以调整速度!
一个用java编写的实现Runnable接口的小程序
线和的应用 runnable接口 runnable接口 runnable接口 runnable接口 runnable接口
本课讲的是如何实现一个Runnable,在一个独立线程上运行Runnable.run()方法.Runnable对象执行特别操作有时叫作任务。 Thread和Runnable都是基础的类,靠他们自己,能力有限。作为替代,Android有强大的基础类,像...
Eclipse V4.2.0用新建Runnable的方法编写的Android数字时钟应用实例。
一个简单的多线程代码示例,Java实现,用于实现同一时刻,只允许一个线程调用执行的代码块或类,即synchronized的如何使用(多线程实现),实现 Runnable