天天看点

从Runnable中的运行时异常说起

前段时间,夜晚突然收到报警,紧急上线排查。由于dba操作不当,大片数据回滚,发生锁表的情况,请求返回时间过长,使得系统打印出大量的rejectedexecutionexception的异常。定位到代码片段类似:

java代码

这里就要说说threadpoolexecutor和arrayblockingqueue了,众所周知arrayblockingqueue类是一个阻塞的队列。当和threadpoolexecutor使用时,threadpoolexecutor会在初始化时开启corepoolsize(也就是上面代码中的10)个线程,去消费队列里的task。当并发量增大,直到corepoolsize全都在执行task,而队列也放满了待处理的task的时候,threadpoolexecutor就会去创建一个新的线程。直至达到线程池中的消费线程达到maximumpoolsize(也就是上面代码里的600)。如果这个时候再有task加入,根据默认的饱和策略,将会抛出rejectedexecutionexception异常。

这次就是由于获取数据库等待时间超长,导致task响应时间变慢,继而线程池中活跃的消费线程堆积到600个线程依然无法应付,才抛出的rejectedexecutionexception。

显然抛出rejectedexecutionexception不是那么的友好,我们在这里可以自定义饱和策略。默认系统饱和策略是抛出异常。

另外当线程故障恢复时,通过日志惊奇的观察到,队列中的线程以串行的方式运行了一短时间,不过很快就正常了。于是在本地写了一个测试。

运行如上的代码,打印日志如下:

可以看到,最后的10个task是以类似串行的方式在运行。

这里的原因在于threadpoolexecutor的中断策略,当runnable中抛出rte时,threadpoolexecutor会将执行当前的runnable的线程dead。由于例子的代码100%会抛出rte,最终的结果就是threadpoolexecutor中存活的消费线程数变为0。threadpoolexecutor创建线程只有在初始化,调用excute等几个主动方法中才会去做,我们任务的提交早在一开始就已经做了,最终导致的结果就是永远只存在一个线程服务这个任务队列。

那么要比较优雅的解决这个问题,可以使用futuretask,futuretask里面会处理运行时异常,不会将其抛出给threadpoolexecutor。