在设置线程池队列长度时,如果长度设置的不合理就无法发挥出多线程的威力。设置线程池的队列长度取决于使用场景;比如全程异步的系统,队列可以设置为0,corePoolSize设置为cpu核数。研究tomcat、Dubbo等业界成熟的产品是如何设置线程队列,分析如何合理设置线程池队列长度。
1.JDK线程池策略
先增加线程至corePoolSize,之后放入队列,如果队列满则增加线程至maximumPoolSize。其实我们不难发现如果队列长度设置无限长度,那么线程池个数将只会增加到corePoolSize,如果corePoolSize个数设置又过小,这样就会无法发挥出多线程的威力。
2.Tomcat线程池策略
Tomcat的线程池队列是无限长度的,但是线程池会一直创建到maximumPoolSize,然后才把请求放入等待队列中
tomcat 任务队列org.apache.tomcat.util.threads.TaskQueue其继承与LinkedBlockingQueue,覆写offer方法。
@Override
public boolean offer(Runnable o) {
//we can't do any checks
if (parent==null) return super.offer(o);
//we are maxed out on threads, simply queue the object
if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);
//we have idle threads, just add it to the queue
if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);
//线程个数小于MaximumPoolSize会创建新的线程。
//if we have less threads than maximum force creation of a new thread
if (parent.getPoolSize()
return super.offer(o);
}
3.Dubbo线程池策略
Dubbo 提供3种线程池模型即:FixedThreadPool、CachedThreadPool(客户端默认的)、LimitedThreadPool(服务端默认的),从源码可以看出,其默认的队列长度都是0,当队列长度为0 ,其使用是无缓冲的队列SynchronousQueue,当运行线程超过maximumPoolSize则拒绝请求。
/**
* 此线程池启动时即创建固定大小的线程数,不做任何伸缩,来源于:Executors.newFixedThreadPool()
*
* @author william.liangf
* @see java.util.concurrent.Executors#newFixedThreadPool(int)
*/
public class FixedThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
//默认队列长度为0
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
return new ThreadPoolExecutor(threads, threads, 0, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue
(queues < 0 ? new LinkedBlockingQueue
: new LinkedBlockingQueue
new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
/**
* 此线程池一直增长,直到上限,增长后不收缩。
*
* @author kimi
*/
public class LimitedThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
//默认队列长度为0
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
return new ThreadPoolExecutor(cores, threads, Long.MAX_VALUE, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue
(queues < 0 ? new LinkedBlockingQueue
: new LinkedBlockingQueue
new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
/**
* 此线程池可伸缩,线程空闲一分钟后回收,新请求重新创建线程,来源于:Executors.newCachedThreadPool()
*
* @author william.liangf
* @see java.util.concurrent.Executors#newCachedThreadPool()
*/
public class CachedThreadPool implements ThreadPool {
public Executor getExecutor(URL url) {
String name = url.getParameter(Constants.THREAD_NAME_KEY, Constants.DEFAULT_THREAD_NAME);
int cores = url.getParameter(Constants.CORE_THREADS_KEY, Constants.DEFAULT_CORE_THREADS);
int threads = url.getParameter(Constants.THREADS_KEY, Integer.MAX_VALUE);
//默认队列长度为0
int queues = url.getParameter(Constants.QUEUES_KEY, Constants.DEFAULT_QUEUES);
int alive = url.getParameter(Constants.ALIVE_KEY, Constants.DEFAULT_ALIVE);
return new ThreadPoolExecutor(cores, threads, alive, TimeUnit.MILLISECONDS,
queues == 0 ? new SynchronousQueue
(queues < 0 ? new LinkedBlockingQueue
: new LinkedBlockingQueue
new NamedThreadFactory(name, true), new AbortPolicyWithReport(name, url));
}
}
总结
线程池的任务队列本来起缓冲作用,但是如果设置的不合理会导致线程池无法扩容至max,这样无法发挥多线程的能力,导致一些服务响应变慢。
队列长度要看具体使用场景,取决服务端处理能力以及客户端能容忍的超时时间等
建议采用tomcat的处理方式,core与max一致,先扩容到max再放队列,不过队列长度要根据使用场景设置一个上限值,如果响应时间要求较高的系统可以设置为0。