基本使用:使用 new ThreadPoolExecutor创建线程池
一般使用new ThreadPoolExecutor
方式,Executors
的几个工厂方法实现也是 new ThreadPoolExecutor
的方式,不过是参数值不一样。
ThreadPoolExecutor
构造方法如下:
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize - 核心线程数。
- maximumPoolSize - 池中允许的最大线程数。核心 + 非核心
- keepAliveTime - 当线程数大于核心线程数时,为空闲线程闲置时间。如果设置allowCoreThreadTimeOut = true,核心线程也会受此值影响。
- unit - keepAliveTime 参数的时间单位。
- workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的Runnable 任务。
常用的workQueue类型:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue、DelayQueue。- 直接传递的队列:
SynchronousQueue
,不会持有任务,通常没有可用线程就创建一个。 - 无界队列:没有设置容量的
LinkedBlockingQueue
,使用与每个任务相对独立,比如网页服务器。 - 有界队列:例如
ArrayBlockingQueue
,可以节省资源。小队列大池,大队列小池,需要自己控制调优。
- 直接传递的队列:
- 线程创建工厂:默认使用
Executors.defaultThreadFactory()
,设置线程组,优先级,名称,是否后台线程等。 - handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
ThreadPoolExecutor.AbortPolicy
:默认策略,抛出RejectedExecutionException
异常。ThreadPoolExecutor.CallerRunsPolicy
:交给提交任务的线程来执行。ThreadPoolExecutor.DiscardPolicy
:丢弃任务。ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列头的任务,可能再次执行失败,继续执行这个策略。
使用execute执行task时,当线程数未到达核心线程数时,新建核心线程;当到达核心线程数量时新的task放到队列里;当队列满时候,新建非核心线程;当线程总数到达maximumPoolSize ,执行丢弃策略。
线程池生命周期管理
生命周期由ctl
是原子整数里面包装的runState
维护。
- 线程池的状态如下:
RUNNING
:接受新任务,处理排队任务。SHUTDOWN
:不接受新任务,但处理排队的任务。STOP
:不接受新任务,不处理排队的任务,中断处理中的任务。TIDYING
:所有的任务都已经终止,workerCount
为零,线程在TIDYING
状态时将运行terminated()
钩子方法。TERMINATED
:terminated()
已完成。
- 状态过度如下:
RUNNING -> SHUTDOWN
:在调用shutdown()时,可能在finalize()中隐式地调用(RUNNING或SHUTDOWN) -> STOP
(停止):在调用shutdownNow()时。SHUTDOWN -> TIDYING
: 当队列和池都为空时STOP -> TIDYING
: 当池空了的时候TIDYING -> TERMINATED
:当 terminated()钩子方法完成时
线程生命周期管理
关注下 ctl
ctl
是个原子整数,它包装了两个字段:
workerCount
:表示有效线程数,是允许开始和不允许停止的worker数量;为了将它们打包成一个int,我们将workerCount
限制为(2^29) -1(约5亿)个线程,而不是(2^31)-1(20亿)个其他可表示的线程。如果将来有这个问题,可以把变量改成AtomicLong,并调整下面的shift/ask常数。但在没有出现需要之前,这段代码使用int会更快更简单一些。(来自注释)runState
:运行状态,表示是否正在运行、关闭等。
下面的代码我把要用到的变量的32位2进制表示写在注释里(注意,仅认为使用的类型为32位int):
//
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 1左移29位是2^29,再减1的二进制表示如下
// 0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// runState 存储在高位
// 1110 0000 0000 0000 0000 0000 0000 0000
private static final int RUNNING = -1 << COUNT_BITS;
// 0000 0000 0000 0000 0000 0000 0000 0000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 0010 0000 0000 0000 0000 0000 0000 0000
private static final int STOP = 1 << COUNT_BITS;
// 0100 0000 0000 0000 0000 0000 0000 0000
private static final int TIDYING = 2 << COUNT_BITS;
// 0110 0000 0000 0000 0000 0000 0000 0000
private static final int TERMINATED = 3 << COUNT_BITS;
// 拆装ctl
// ~CAPACITY为 1110 0000 0000 0000 0000 0000 0000 0000,
// ctl与非CAPACITY按位与,相当于拿到高3位,即拆出runState的值
private static int runStateOf(int c) { return c & ~CAPACITY; }
// ctl与CAPACITY按位与,相当于拿到低29位,即拆出来workerCount的值
private static int workerCountOf(int c) { return c & CAPACITY; }
// runState和workerCount按位或,一个高3位为有效值,一个低29为有效值 ,相当于把两个值装到一起。
private static int ctlOf(int rs, int wc) { return rs | wc; }
/*
* 因workerCount处于低位,而runState处于高位,所以直接判断ctl的大小而无需拆ctl即可判断状态值,所以有如下方法
*/
private static boolean runStateLessThan(int c, int s) {
return c < s;
}
private static boolean runStateAtLeast(int c, int s) {
return c >= s;
}
// 观察可知,SHUTDOWN所有位均为0,仅有RUNNING状态的符号位为1,故小于SHUTDOWN即判断是RUNNING状态
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
扩展钩子
beforeExecute(Thread, Runnable)
::任务执行前调用afterExecute(Runnable, Throwable)
:任务执行后调用terminated()
:Executor
完全关闭后调用。
上面的理解只是理解ThreadPoolExecutor的基础,在此基础上我们再展开来看。
另:线程池配置 [转载自文章底部链接]
- DB连接池配置
- 最小连接数=(平均QPS* QPS平均RT +平均TPS* TPS平均RT)/业务机器数
- 最大连接数=(峰值QPS* QPS平均RT +峰值TPS* TPS平均RT)/业务机器数;
如果业务代码中没有另起多线程,那么也可以使用公式:最大连接数= 容器处理请求的线程池大小。
另外,如果需要,也可以在此公式的基础上预留一些buffer。
- Java线程池配置
2.1 CPU密集型任务的线程池配置
因为cpu执行速度非常快,往往切换线程上下文环境所耗费的时间比执行代码花费的时间更长,所以CPU密集型任务的线程数应该尽量保持和CPU核数一致,以减少线程切换带来的性能损耗。
2.2 IO密集型任务的线程池配置
IO密集型任务的主要性能瓶颈在于等待IO结果,当遇到任务中存在从DB中读取数据,从缓存中读取数据时,就可以认为是IO密集型任务。一般而言业务系统配置线程池基本上都会采用此模型配置