Executor 线程池由于task被hold住抛出RejectedExecutionException

摘要:ThreadPoolExecutor在task超时后task.cancel(true)来取消任务,但是还是会出现RejectedExecutionException 新task被拒绝的问题。

最近项目中使用ThreadPoolExecutor处理并发业务,每个task执行db层的业务。我们的设计是这样的:

1、task设置超时时间,避免task无限期执行;

2、在超时异常捕获后,我们task.cancel(true); 期待能把这个超时task取消,给后续新任务使用(归还线程池)。

本以为这样就ok了,但是RejectedExecutionException 问题还是出现了。

没道理啊?已经取消耗时任务了,为什么pool还是拒绝新task呢!


下面依据代码具体分析整个过程:

场景:由于某次意外db出现问题,db的操作处于失败重试机制中(由于配置重试策略等等,可能10分钟才会告诉你查询失败了)。

分析:至此,那么超时就发生了,进入2中异常捕获,调用cancle视图取消task。新的请求再从pool申请启动task,此时发生了RejectedExecutionException 。说明pool中已经没idle的资源可用,那么cancle就是失败了!

启动task代码片段:

FutureTask task = new FutureTask(
new RemoteGetCallable(hTablePool, tableName, family,
rowKey, colums));
try {
executor.execute(task);
} catch (RejectedExecutionException e) {
task.cancel(true);
throw new Exception("-----TASK POOL IS FULL-----" + "@"
+ tableName + ":" + rowKey);
}
try {
value = task.get(REMOTE_TIME_OUT, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
task.cancel(true);
System.out.println(task.isCancelled());
throw new Exception(e.getClass().getName() + "@" + tableName
+ ":" + rowKey);
}

果断查看FutureTask.cancel方法,确实“Attempts to cancel execution of this task. This attempt will fail if the task has already completed, has already been cancelled, or could not be cancelled for some other reason.” 最后那句说明有可能无法cancle,结合我们的场景分析(db操作被hold,无法返回),这个task等待db层的响应导致无法cancle。


构造ThreadPoolExecutor片段:

ExecutorService executor = new ThreadPoolExecutor(
Integer.parseInt(ConfigUtil
.getValue("executor.thread.count")),
Integer.parseInt(ConfigUtil
.getValue("executor.thread.count")),
Integer.parseInt(ConfigUtil.getValue("executor.thread.tti")),
TimeUnit.SECONDS, new SynchronousQueue(),
new ThreadPoolExecutor.AbortPolicy());


现在问题的本质就是task无法取消放回线程池重用。那么就可以针对这个问题寻求解决办法了!

方向:

1、寻求能够取消task的方式(重用思路);

2、寻求干掉无法取消task,新增task到pool(除旧换新)。

3、当然,最好的办法是解决db层hold住的问题!


我们采用了第二个方向,因为db层有可能因为大量数据入库导致不可预测的性能问题(由于入库占用了大量性能),从而导致部分访问被hold住,而且时间点也不可控。

具体办法:

修改ThreadPoolExecutor的Blockingqueue为ArrayBlockingQueue,修改RejectedExecutionHandler为DiscardOldestPolicy。解释:队列使用数组可以控制大小,避免list无界可能带来的oom问题;DiscardOldestPolicy策略是在池子满的时候删除旧的task。


可能带来的负面影响或需要测试的点:

1、由于采用了DiscardOldestPolicy策略,那么峰值时如果不能应对,可能导致正常task被删除的风险。简单举例不考虑队列问题,比如:100的池子,此时100个task在查数据库,同时101个请求来了,会从池子中删除一个正在run的task(正常,很无辜),然后101开始运行。

2、由于DiscardOldestPolicy策略删除old task,可能导致oom(猜测)。分析:由于从队列头删除task,那么这个被删除的task占用的资源是否被gc回收了呢,还是等hold结束才释放?没有查到具体的说明。如果是后者,而且服务访问压力很大,那么oom的可能很大了!






你可能感兴趣的:(多线程)