最近的工作中,因为程序需要大量进行数据库查询,我使用ArrayBlockingQueue模仿数据库连接池,里面初始化了固定数量的数据库连接对象。
我的业务中,存在主任务生成子任务,然后通过Future对象等待子任务返回结果的情况,为了避免已经在work队列的主任务一直占着cpu等待子任务返回,而子任务由于在task队列中,因此得不到cpu资源,无法执行。我使用了cache线程池,让所有任务都能被封装为thread,可以得到cpu时间。
这个设计在测试环境的小数据集下运行是正常的,任务都能执行完,但提交到生产环境,就会在运行一段时候后出问题,现象是程序日志停止,对象gc也停止,似乎程序停止或者在被阻塞住了。
后来通过jstack命令,将程序的线程调用栈帧拿来分析发现大量的线程是处于packing状态,而且他们都是在ArrayBlockingQueue的take()方法上阻塞,数量有436个。
因此怀疑,可能是有方法将A连接池中的dbHelper用完后,放到了B连接池中,导致最后A连接池中dbHelper全部泄露到了其他地方,因此没有线程能再获取到数据库连接。但对代码检查后,没有发现这种问题。
接下来再看线程栈帧,发现线程cache线程池的线程id已经到了893,证明cache线程池中有大量线程生产,这些线程都处于packing状态。只有一个线程是runnable状态,他获得了dbHelper,正在查询数据库。
于是我怀疑,是因为生成了大量线程,这些线程都需要分配cpu时间才能执行,但每个线程都只能分配到很少的cpu时间,导致持有dbHelper的线程迟迟执行不完,它无法释放dbHelper,其他线程也只能浪费着cpu时间等着。
接下来修改代码,主任务还是在cache线程池中,但其产生的子任务,放到另外一个fix线程池中执行,这样cpu就会将fix的worker队列中的查询先执行完,将dbHelper还回连接池,再去执行新的子任务,而不会创建过多的thread去等待dbHelper。修改完成后,重新执行程序,终于在日志中发现了新的日志,问题被解决。
其实到最后,我也不知道自己的分析是否完全正确。