一次并发编程问题复盘
别人都是代码在测试环境运行正常,然后发布生产环境后发现异常。我这好家伙,在idea中启动一切正常,一旦在命令行启动用Java命令启动,直接就不一样了
问题描述
最近在开发一个项目,其中有一个场景就是启动多个线程来执行多个(2000左右)任务,可在开发的过程中我发现一个奇怪的现象。在我用idea测试我的程序时,无论是启1个线程、10个线程、还是50个线程……和1个任务、100个任务、还是2000个任务,程序运行一切正常,运行结果也正是我所期待的。由于这个项目需要提供一个命令行工具的版本,所以我正一副准备搞完下班的样子将程序打包,然后用Java命令启动测试一下开发已久的成果。结果,果然令我大吃一惊!启动是正常了,可运行速度却只有在idea启动的一层!这是为何,简直让我百思不得其姐啊~~~
通用jstack查线程状态后,有以下发现:
- idea工具启动程序时,core线程全部启动,且运行状态都是RUNNING状态
- 通过命令行Java命令启动时,core线程全部启动,可运行状态都是WAITING(parking)状态
- 程序并无死锁和较大耗时操作
一开始我以为是线程池使用有问题,所以就从线程池入手查找问题原因。这一次让我对ThreadPollExecutor也有了更深的理解,首先让我们来回顾一下它的基本知识点
ThreadPoolExecutor介绍
熟练掌握和使用ThreadPoolExecutor类是每个Java开发人员必备的技能,他能够帮助开发人员合理的管理和和执行并行任务,使用线程池不仅免去了创建和销毁线程等繁琐的线程管理工作,更重要的是极大的提升了程序的响应速度,并且在基于ThreadPoolExecutor可以扩展出更多的功能,例如ScheduledThreadPoolExecutor等
- corePoolSize & maximumPoolSize: 核心线程数和最大线程数,线程池会根据这两个参数动态调整线程数量。
若提交任务时线程池中的线程数小于corePoolSize则直接新建线程运行任务
若提交任务时线程池中的线程数大于corePoolSize且小于maximumPoolSize,任务队列满时会创建新的线程运行任务,否则存入任务队列 - keepAliveTime & unit: 线程存活时间和时间单位,若当前线程池线程数量大于corePoolSize,空闲的线程或超过keepAliveTime的线程会被终止
- workQueue: 任务队列,若提交任务时线程池线程数量大于maximumPoolSize,则任务会被放入任务队列中等待执行。当线程池线程数量和workQueue都达到最大值时,新提交的任务将会被拒绝
- handler: 拒绝策略(RejectedExecutionHandler),当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略,保护线程池
查找问题原因
在解决问题的过程中,我发现了好几个坑,下面就来一一介绍
1. 线程池的提交方法
向ThreadPoolExecutor中提交任务有两种方法,分别是execute和submit。我一开始没有研究他们到底有什么区别,默认都是用submit提交,因为“submit”这个方法名给我的感觉就是提交到任务队列,至于什么时候执行交给线程池去判断。
然鹅,现实给了我一个响亮的耳光,当我苦于思索最开始遇到的问题是什么原因时,其中**“Idea和Java命令行启动都是一切正常”** 这个现象就是在这里掉坑里了,其实他们的现象并不一样,因为submit提交的任务抛出的异常会被线程池catch掉,调用FutureTask的get方法时,才会抛出异常!!!
当用submit提交任务时,任务首先会被封装成一个FutureTask 再调用execute方法执行
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
我们都知道,提交新的任务(Runnable)在执行时,肯定会调用run方法来执行具体逻辑,那么这个FutureTask的run方法到底干了什么呢
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;</