总结一下最近工作中遇到的线程池相关问题



背景:

在业务代码中,新建线程池的代码如下:

ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(0, 100, 0L, 
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());

我们的配置是

corePoolSize=0;
 maximumPoolSize=100;
 keepAliveTime,=0L;
 使用LinkedBlockingQueue作为线程池的阻塞队

在运行后发现,代码中一共提交了5个task,只有第一个submit的task被成功执行了,其他的都没有正常运行。
通过调用getActiveCount(),getTaskCount();方法。获取到的结果同观察到的一样,active的task数量为1,task的数量总数为5。
接下来,我们调用getQueue()方法,发现queue的size为4,这与我们的总数-被执行线程数刚好契合。遍历queue中的task后,发现其他的task果然都在阻塞队列中被阻塞住了。

问题分析

通过网上查资料和阅读源码,了解到如下信息。
线程池在接收到新的task后,处理逻辑如下:

  1. 首先判断当前的线程数量是否小于corePoolSize,如果是,线程池会创建新的核心线程(coreThread)执行task;
  2. 如果线程池中的线程数量大于corePoolSize,会将该task放入我们设置的阻塞队列中。
  3. 如果阻塞队列也满了,且线程中线程数量小于maximumPoolSize;线程池创建非核心线程执行task(新创建的非核心线程会直接执行当前被submit的task,而不是先从阻塞队列中尝试获取);
  4. 当非核心线程运行结束,会从阻塞队列中尝试获取task,如果获取不到,经过我们设置的keepAliveTime后,非核心线程会被回收。

由于我们将corePoolSize设为了0,线程池会首先执行addWorker方法,创建新的临时线程来执行task;由于我的每个task里面的业务逻辑都包含一个while循环(需要不断从数据库中抓取数据)。因此,第一个task永远不会结束,当后面的四个task被提交到线程池后,由于当前线程数已经大于corePoolSize,新的任务将会被放入阻塞队列中,永远不会被执行到。

if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }

解决办法:

开始的时候,想的是既然阻塞队列满了之后,新提交的task才会创建非核心线程去执行,,那我不如将阻塞队列置为空.但设置后,代码却报错了,查看源码后发现线程池在这里是有限制的。

If (workQueue == null || threadFactory == null
		 || handler == null)
            throw new NullPointerException();

再继续查阅相关资料,发现对于我的这种情况,更适合使用SynchronousQueue作为阻塞队列。

SynchronousQueue
也是一个队列来的,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时候),如果当前没有人想要消费产品(即当前没有线程执行take),此生产线程必须阻塞,等待一个消费线程调用take操作,take操作将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递)

简单来讲就是,SynchronousQueue其实是一个虚假的“队列”,它没有容量,也不会存储元素每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
当我们使用SynchronousQueue作为阻塞队列后,线程池的执行逻辑则变为了,只要当前线程数小于maximumpoolsize,线程池都会创建新的线程去执行新提交的task(小于corepoolsize的创建核心线程,大于的创建非核心线程)。当线程数大于最大线程数后,线程池会执行相应的饱和策略。

后记:

后面还遇到了执行完的线程没有及时被回收的问题,等过两天总结下继续发出来。