`
8821249
  • 浏览: 67309 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

从Runnable中的运行时异常说起

    博客分类:
  • java
阅读更多
前段时间,夜晚突然收到报警,紧急上线排查。由于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。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics