线程池参数
参数名 | 作用 |
---|---|
corePoolSize | 核心线程数 |
maximumPoolSize | 最大线程数 |
keepAliveTime | 线程池中超过corePoolSize数目的空闲线程最大存活时间;当设置allowCoreThreadTimeOut=true会使得核心线程也超时被释放 |
TimeUnit | keepAliveTime时间单位 |
workQueue | 阻塞任务队列 |
threadFactory | 新建线程的工厂 |
rejectedExecutionHandler | 当提交任务数超过maxmumPoolSize + workQueue之和时,任务会交给rejectedExecutionHandler来处理 |
allowCoreThreadTimeout | 默认为false,设置为true时,当core thread超过过期时间时也会被释放 |
如何设置参数
默认值
corePoolSize=1
queueCapacity=Integer.MAX_VALUE
maxPoolSize=Integer.MAX_VALUE
keepAliveTime=60s
allowCoreThreadTimeout=false
rejectedExecutionHandler=AbortPolicy()
参考值
QPS :每秒的任务数,假设为500~1000
cost_time :每个任务花费时间,假设为0.1s
max_response_time :系统允许容忍的最大响应时间,假设为1s
max_capacity :每秒系统能够支撑当最大容量(通常是DB/IO/CPU资源限制)
得出数据
corePoolSize = 每秒需要多少个线程处理?
threadcount = QPS / ( 1s/cost_time ) = QPS * cost_time = (500~1000)*0.1 = 50~100 个线程。corePoolSize设置应该大于50
根据8020原则,如果80%的每秒任务数小于800,那么corePoolSize设置为80即可
queueCapacity = (corePoolSize / cost_time) * max_response_time
计算可得 queueCapacity = 80/0.1 * 1 = 80。意思是队列里的线程可以等待1s,超过了的需要新开线程来执行
切记不能设置为Integer.MAX_VALUE,这样队列会很大,线程数只会保持在corePoolSize大小,当任务陡增时,不能新开线程来执行,响应时间会随之陡增。
最大线程数 = (最大任务数-队列容量)/每个线程每秒处理能力
maxPoolSize = (min(max(QPS),max_capacity)- queueCapacity) / (1s/cost_time)
计算可得 maxPoolSize = (1000-80)/10 = 92
rejectedExecutionHandler:根据具体情况来决定,任务不重要可丢弃,任务重要则要利用一些缓冲机制来处理
keepAliveTime和allowCoreThreadTimeout采用默认通常能满足
问题
Q: 为什么不直接将队列大小设小一点呢,这样就能直接创建线程处理任务了?
A: 直觉上来说是这样的,但是任务类型分为CPU密集型和IO密集型。如果是CPU密集型任务,那么CPU将会是瓶颈,创建过多线程反而会适得其反,因为在CPU的维度上,每次同时执行任务的数量与核数成正比,如果线程数过多,那么线程上下文切换的时间消耗将会增加,反而造成处理效率的低下。如果是IO密集型,那么可以适当的增加corePoolSize。
扩展
ThreadPoolExecutor扩展点
#在任务执行之前执行
beforeExecute(java.lang.Thread,java.lang.Runnable)
#在任务完成之后执行
afterExecute(java.lang.Runnable,java.lang.Throwable)
#当线程池被关闭时执行
terminated()
一个参考(记录任务执行的时间)
private ThreadLocal<Long> taskExecuteStart = new ThreadLocal<>();
@Override
protected void beforeExecute(Thread t, Runnable r) {
taskExecuteStart.set(System.currentTimeMillis());
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
Long start = taskExecuteStart.get();
Long executeTime = System.currentTimeMillis() - start;
//记录执行时间
taskExecuteStart.remove();
}
@Override
protected void terminated() {
//记录日志和监控
}
ThreadFactory扩展点
public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}
ThreadFactory主要用于创建线程,因此我们可以通过Thread来扩展某些功能,比如catch异常,为线程设置名字,设置线程执行优先级等等。
一个参考(设置线程名字,以及设置默认异常catch)
class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger mThreadNum;
private final String mPrefix;
private final boolean mDaemo;
private final ThreadGroup mGroup;
public NamedThreadFactory(String prefix) {
this(prefix, true);
}
public NamedThreadFactory(String prefix, boolean daemo) {
this.mThreadNum = new AtomicInteger(1);
this.mPrefix = prefix + "-thread-";
this.mDaemo = daemo;
SecurityManager s = System.getSecurityManager();
this.mGroup = s == null ? Thread.currentThread().getThreadGroup() : s.getThreadGroup();
}
public Thread newThread(Runnable runnable) {
String name = this.mPrefix + this.mThreadNum.getAndIncrement();
Thread ret = new Thread(this.mGroup, runnable, name, 0L);
ret.setUncaughtExceptionHandler(DefaultUncaughtExceptionHandler.INSTANCE);
ret.setDaemon(this.mDaemo);
return ret;
}
public ThreadGroup getThreadGroup() {
return this.mGroup;
}
}
enum DefaultUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
INSTANCE;
@Override
public void uncaughtException(Thread t, Throwable e) {
//记录日志和监控
}
}