1.使用线程池的原因
- 减少线程创建和销毁的开销,每个工作线程都可重复的使用,执行多个任务;
- 根据系统的能力设置线程的数量,访问线程数量过大造成系统内存的使用率过高;
- 系统响应能力,有效的避免了很多情况下线程的创建所消耗的时间。
2.线程池的分类
Executors | 此类是个工厂类/工具类,提供Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的一些实用方法。 |
ExecutorService | 真正的线程池接口。 |
ThreadPoolExecutor | ThreadPoolExecutor是ExecutorService的一个实现类,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。 |
类型
|
说明
|
---|---|
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } | 单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 |
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } | 固定大小的线程池。 corePoolSize和maximumPoolSize设置为参数中指定的值,并且创建的线程池不会超时。 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 |
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } | 可缓存的线程池。 线程池的corePoolSize设置为0,maximumPoolSize设置为Integer.MAX_VALUE,并将超时设置为1分钟,这种方式创建的线程池可以被无限扩展,并且当需求降低时会自动收缩。 如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 |
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } | 固定大小的线程池。此线程池支持定时以及周期性执行任务的需求。 |
3.ThreadPoolExecutor构造函数详解
ThreadPoolExecutor是Executors类的底层实现,ThreadPoolExecutor的完整构造函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
.corePoolSize = corePoolSize;
this
.maximumPoolSize = maximumPoolSize;
this
.workQueue = workQueue;
this
.keepAliveTime = unit.toNanos(keepAliveTime);
this
.threadFactory = threadFactory;
this
.handler = handler;
}
|
参数
|
说明
|
---|---|
int corePoolSize | 核心池的大小:在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到阻塞队列当中。 |
int maximumPoolSize | 线程池最大线程数:线程池允许创建的最大线程数。如果阻塞队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。 |
long keepAliveTime | 线程活动保持时间:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。 keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。 如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有意义。 反之,如果有界BlockingQueue数值又较小,线程核心数较小,同时keepAliveTime又设的很小,如果任务频繁,那么系统就会频繁的申请回收线程。 |
TimeUnit unit | 线程活动保持时间的单位: TimeUnit.DAYS; //天 |
BlockingQueue<Runnable> workQueue | 阻塞队列:用于保存等待执行的任务的阻塞队列。
|
ThreadFactory threadFactory | 线程工厂:每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。默认的线程工厂将创建一个新的、非守护的线程,并且包含特殊的配置信息。通过指定一个线程工厂方法,可以定制线程池的配置信息。在ThreadFactory中只定义一个方法newThread,每当线程池需要创建一个新线程时都会调用这个方法。 |
RejectedExecutionHandler handler | 拒绝策略:当线程池和阻塞队列都满了,或者线程池调用了shutdown()、shutdownNow(),说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。默认拒绝策略是AbortPolicy,表示无法处理新任务时抛出异常RejectedExecutionException。
|
4.线程池的状态及流转
线程池的状态:
- private static final int RUNNING = -1 << COUNT_BITS; 接收新的任务并且处理队列中的任务。
- private static final int SHUTDOWN = 0 << COUNT_BITS; 不接收新的任务,但是处理队列中的任务。
- private static final int STOP = 1 << COUNT_BITS; 不接收新的任务,也不处理队列中的任务,并且中断正在运行中的任务。调用方法shutdownNow();
- private static final int TIDYING = 2 << COUNT_BITS; 所有的任务都已经终止,工作的线程数量为0,所有的线程过渡到TIDYING状态,将会调用terminated()钩子方法。
- private static final int TERMINATED = 3 << COUNT_BITS; terminated()方法已经完成。
状态过渡流程:
- RUNNING -> SHUTDOWN: 调用shutdown()
- (RUNNING or SHUTDOWN) -> STOP: 调用shutdownNow()
- SHUTDOWN -> TIDYING: 当队列和线程池都为空时
- STOP -> TIDYING: 当线程池为空
- TIDYING -> TERMINATED: 所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,当terminated()方法执行完成线程池状态变为TERMINATED
5.线程池处理流程
当提交一个新任务到线程池时,线程池的处理流程如下:
- 首先线程池判断核心线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。corePoolSize 为 0 时是一种特殊情况,此时即使工作队列没有饱和,向线程池第一次提交任务时仍然会创建新的线程。
- 其次线程池判断是否有空闲线程?若是,则使用空闲线程来执行任务。否则进入下个流程。
- 接着线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。
- 最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务。满了,则交给饱和策略来处理这个任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// ctl是一个AtomInteger,低29位表示线程数workerCount,高3位表示线程池运行状态runState
private
final
AtomicInteger ctl =
new
AtomicInteger(ctlOf(RUNNING,
0
));
private
static
final
int
COUNT_BITS = Integer.SIZE -
3
;
// Integer.SIZE = 32
private
static
final
int
CAPACITY = (
1
<< COUNT_BITS) -
1
;
// 00011111111111111111111111111111
// runState is stored in the high-order bits
private
static
final
int
RUNNING = -
1
<< COUNT_BITS;
// 11100000000000000000000000000000
private
static
final
int
SHUTDOWN =
0
<< COUNT_BITS;
// 00000000000000000000000000000000
private
static
final
int
STOP =
1
<< COUNT_BITS;
// 00100000000000000000000000000000
private
static
final
int
TIDYING =
2
<< COUNT_BITS;
// 01000000000000000000000000000000
private
static
final
int
TERMINATED =
3
<< COUNT_BITS;
// 01100000000000000000000000000000
/**
* 这个方法用于取出runState的值 因为CAPACITY值为:00011111111111111111111111111111
* ~为按位取反操作,则~CAPACITY值为:11100000000000000000000000000000
* 再同参数做&操作,就将低29位置0了,而高3位还是保持原先的值,也就是runState的值
*
* @param c,该参数为存储runState和workerCount的int值
* @return runState的值
*/
private
static
int
runStateOf(
int
c) {
return
c & ~CAPACITY;
}
/**
* 这个方法用于取出workerCount的值,因为CAPACITY值为:00011111111111111111111111111111,
* 所以&操作将参数的高3位置0了,保留参数的低29位,也就是workerCount的值
*
* @param c,存储runState和workerCount的int值
* @return workerCount的值
*/
private
static
int
workerCountOf(
int
c) {
return
c & CAPACITY;
}
/**
* 将runState和workerCount存到同一个int中
*
* @param rs,runState移位过后的值,负责填充返回值的高3位
* @param wc,workerCount移位过后的值,负责填充返回值的低29位
* @return 两者或运算过后的值
*/
private
static
int
ctlOf(
int
rs,
int
wc) {
return
rs | wc;
}
// 只有RUNNING状态会小于0
private
static
boolean
isRunning(
int
c) {
return
c < SHUTDOWN;
}
|
execute
给线程池加入任务的方法为execute方法,该方法策略大概分三步:
1)活动线程小于corePoolSize的时候创建新的线程;
2)活动线程大于corePoolSize时都是先加入到任务队列当中;
3)任务队列满了再去启动新的线程,如果线程数达到最大值就拒绝任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
public
void
execute(Runnable command) {
if
(command ==
null
)
throw
new
NullPointerException();
int
c = ctl.get();
if
(workerCountOf(c) < corePoolSize) {
// 当前线程数小于核心线程数corePoolSize,新建核心线程处理任务
if
(addWorker(command,
true
))
// 直接启动新的线程。第二个参数true,addWorker中会重新检查workerCount是否小于corePoolSize
return
;
c = ctl.get();
}
// 活动线程数 >= corePoolSize
// runState为RUNNING && 队列未满
if
(isRunning(c) && workQueue.offer(command)) {
int
recheck = ctl.get();
// 双重检查
if
(! isRunning(recheck) && remove(command))
// 非RUNNING状态,则从workQueue中移除任务并拒绝任务
reject(command);
// 采用线程池指定的策略拒绝任务
else
if
(workerCountOf(recheck) ==
0
)
// 线程池处于RUNNING状态 || 线程池处于非RUNNING状态但是任务移除失败
addWorker(
null
,
false
);
// addWorker()这行代码是为了SHUTDOWN状态下没有活动线程了,但是队列里还有任务没执行这种特殊情况。
// 添加一个null任务是因为SHUTDOWN状态下,线程池不再接受新任务
// 两种情况:
// 1.非RUNNING状态拒绝新的任务
// 2.队列满了启动新的线程失败(workCount > maximumPoolSize)
}
else
if
(!addWorker(command,
false
))
reject(command);
}
|
addWorker
addWorker即创建新线程。检查在当前线程池状态和限制下能否创建一个新线程,如果可以,会相应改变workerCount,每个worker都会运行他们的firstTask。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
// 第一个参数是需要运行的任务
// 第二个参数是为了区分核心线程和非核心线程,用来确定线程池的边界是corePoolSize还是maxPoolSize
// 本方法的作用就是新建一个worker线程并启动
private
boolean
addWorker(Runnable firstTask,
boolean
core) {
retry:
for
(;;) {
int
c = ctl.get();
int
rs = runStateOf(c);
// Check if queue empty only if necessary.
// 这条语句等价:rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || workQueue.isEmpty())
// 满足下列调价则直接返回false,线程创建失败:
// rs > SHUTDOWN:STOP || TIDYING || TERMINATED 此时不再接受新的任务,且所有任务执行结束
// rs = SHUTDOWN:firstTask != null 此时不再接受任务,但是仍然会执行队列中的任务
// rs = SHUTDOWN:firstTask == null 见execute方法的addWorker(null, false),任务为null && 队列为空
// 最后一种情况也就是说SHUTDOWN状态下,如果队列不为空还得接着往下执行,为什么?add一个null任务目的到底是什么?
// 看execute方法只有workCount == 0的时候firstTask才会为null,结合这里的条件就是线程池SHUTDOWN了不再接受新任务,
// 但是此时队列不为空,那么还得创建线程把任务给执行完才行。
if
(rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask ==
null
&& ! workQueue.isEmpty()))
return
false
;
// 走到这的情形:
// 1.线程池状态为RUNNING
// 2.SHUTDOWN状态,但队列中还有任务需要执行
for
(;;) {
// 循环确保CAS操作成功
int
wc = workerCountOf(c);
if
(wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
return
false
;
if
(compareAndIncrementWorkerCount(c))
// 成功增加workerCount则跳出外层循环,开始新建线程
break
retry;
c = ctl.get();
// 重读 ctl
if
(runStateOf(c) != rs)
// 如果线程池的状态发生变化则重试循环
continue
retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// wokerCount递增成功
boolean
workerStarted =
false
;
boolean
workerAdded =
false
;
Worker w =
null
;
try
{
final
ReentrantLock mainLock =
this
.mainLock;
w =
new
Worker(firstTask);
// 新建worker线程
final
Thread t = w.thread;
if
(t !=
null
) {
mainLock.lock();
// 并发的访问线程池workers对象必须加锁
try
{
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int
c = ctl.get();
int
rs = runStateOf(c);
if
(rs < SHUTDOWN || (rs == SHUTDOWN && firstTask ==
null
)) {
if
(t.isAlive())
// 预检查 t 是否是startable
throw
new
IllegalThreadStateException();
workers.add(w);
// 将新建的线程放入线程池中,其实就是一个HashSet
int
s = workers.size();
if
(s > largestPoolSize)
// 更新largestPoolSize
largestPoolSize = s;
workerAdded =
true
;
}
}
finally
{
mainLock.unlock();
}
// 启动新添加的线程,这个线程首先执行firstTask,然后不停的从队列中取任务执行
// 当等待keepAliveTime还没有任务执行则该线程结束。见runWorker和getTask方法的代码。
if
(workerAdded) {
t.start();
// 如果成功加入线程池则启动线程,最终执行的是ThreadPoolExecutor的runWoker方法
workerStarted =
true
;
}
}
}
finally
{
if
(! workerStarted)
addWorkerFailed(w);
}
return
workerStarted;
// 返回线程是否启动成功
}
|
内部类Worker是对任务的封装,所有submit的Runnable都被封装成了Worker,它本身也是一个Runnable, 然后利用AQS框架实现了一个简单的非重入的互斥锁, 实现互斥锁主要目的是为了中断的时候判断线程是在空闲还是运行,可以看后面shutdown和shutdownNow方法的分析。Worker中获取和释放锁相关代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// Worker的构造方法
Worker(Runnable firstTask) {
setState(-
1
);
// 抑制线程中断setState(-1),inhibit interrupts until runWorker
this
.firstTask = firstTask;
this
.thread = getThreadFactory().newThread(
this
);
}
// state只有0和1,互斥
// 之所以不用ReentrantLock是为了避免任务执行的代码中修改线程池的变量,如setCorePoolSize,因为ReentrantLock是可重入的。
protected
boolean
tryAcquire(
int
unused) {
if
(compareAndSetState(
0
,
1
)) {
setExclusiveOwnerThread(Thread.currentThread());
return
true
;
// 成功获得锁
}
return
false
;
// 线程进入等待队列
}
protected
boolean
tryRelease(
int
unused) {
setExclusiveOwnerThread(
null
);
setState(
0
);
return
true
;
}
|
runWorker
任务添加成功后实际执行的是runWorker这个方法,这个方法非常重要,简单来说它做的就是:
1)第一次启动会执行初始化传进来的任务firstTask;
2)然后会从workQueue中取任务执行,如果队列为空则等待keepAliveTime这么长时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
final
void
runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask =
null
;
w.unlock();
// Worker的构造函数中抑制了线程中断setState(-1),所以这里需要unlock从而允许中断
// 用于标识是否异常终止,finally中processWorkerExit的方法会有不同逻辑
// 为true的情况:1.执行任务抛出异常;2.被中断。
boolean
completedAbruptly =
true
;
try
{
// 如果getTask返回null那么getTask中会将workerCount递减,如果异常了这个递减操作会在processWorkerExit中处理
while
(task !=
null
|| (task = getTask()) !=
null
) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if
((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted())
wt.interrupt();
try
{
beforeExecute(wt, task);
// 任务执行前可以插入一些处理,子类重载该方法
Throwable thrown =
null
;
try
{
task.run();
// 执行用户任务
}
catch
(RuntimeException x) {
thrown = x;
throw
x;
}
catch
(Error x) {
thrown = x;
throw
x;
}
catch
(Throwable x) {
thrown = x;
throw
new
Error(x);
}
finally
{
afterExecute(task, thrown);
// 和beforeExecute一样,留给子类去重载
}
}
finally
{
task =
null
;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly =
false
;
}
finally
{
processWorkerExit(w, completedAbruptly);
// 结束线程的一些清理工作
}
}
|
getTask
runWorker方法里有一段会调用getTask判断,主要用来取出队列中的任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
private
Runnable getTask() {
boolean
timedOut =
false
;
// Did the last poll() time out?
retry:
for
(;;) {
int
c = ctl.get();
int
rs = runStateOf(c);
// Check if queue empty only if necessary.
// 1.rs > SHUTDOWN 所以rs至少等于STOP,这时不再处理队列中的任务
// 2.rs = SHUTDOWN 所以rs>=STOP肯定不成立,这时还需要处理队列中的任务除非队列为空
// 这两种情况都会返回null让runWoker退出while循环也就是当前线程结束了,所以必须要decrement wokerCount
if
(rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
// 递减workerCount值
return
null
;
}
// 标记从队列中取任务时是否设置超时时间
boolean
timed;
// Are workers subject to culling?
// 1.RUNING状态
// 2.SHUTDOWN状态,但队列中还有任务需要执行
for
(;;) {
int
wc = workerCountOf(c);
// 1.core thread允许被超时,那么超过corePoolSize的线程必定有超时
// 2.allowCoreThreadTimeOut == false && wc > corePoolSize时,
// 一般都是这种情况,core thread即使空闲也不会被回收,只要超过的线程才会
timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 从addWorker可以看到一般wc不会大于maximumPoolSize,所以更关心后面半句的情形:
// 1. timedOut == false 第一次执行循环, 从队列中取出任务不为null方法返回 或者 poll出异常了重试
// 2.timeOut == true && timed == false:看后面的代码workerQueue.poll超时时timeOut才为true,
// 并且timed要为false,这两个条件相悖不可能同时成立(既然有超时那么timed肯定为true)
// 所以超时不会继续执行而是return null结束线程。(重点:线程是如何超时的???)
if
(wc <= maximumPoolSize && !(timedOut && timed))
break
;
if
(compareAndDecrementWorkerCount(c))
// workerCount递减,结束当前thread
return
null
;
c = ctl.get();
// Re-read ctl
if
(runStateOf(c) != rs)
// 需要重新检查线程池状态,因为上述操作过程中线程池可能被SHUTDOWN
continue
retry;
// else CAS failed due to workerCount change; retry inner loop
}
try
{
// 1.以指定的超时时间从队列中取任务
// 2.core thread没有超时
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
if
(r !=
null
)
return
r;
timedOut =
true
;
// 超时
}
catch
(InterruptedException retry) {
timedOut =
false
;
// 线程被中断重试
}
}
}
|
processWorkerExit
线程退出会执行processWorkerExit()方法做一些清理工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
private
void
processWorkerExit(Worker w,
boolean
completedAbruptly) {
// 正常的话再runWorker的getTask方法workerCount已经被减一了
if
(completedAbruptly)
decrementWorkerCount();
final
ReentrantLock mainLock =
this
.mainLock;
mainLock.lock();
try
{
// 累加线程的completedTasks
completedTaskCount += w.completedTasks;
// 从线程池中移除超时或者出现异常的线程
workers.remove(w);
}
finally
{
mainLock.unlock();
}
// 尝试停止线程池
tryTerminate();
int
c = ctl.get();
// runState为RUNNING或SHUTDOWN
if
(runStateLessThan(c, STOP)) {
// 线程不是异常结束
if
(!completedAbruptly) {
// 线程池最小空闲数,允许core thread超时就是0,否则就是corePoolSize
int
min = allowCoreThreadTimeOut ?
0
: corePoolSize;
// 如果min == 0但是队列不为空要保证有1个线程来执行队列中的任务
if
(min ==
0
&& !workQueue.isEmpty())
min =
1
;
// 线程池还不为空那就不用担心了
if
(workerCountOf(c) >= min)
return
;
// replacement not needed
}
// 1.线程异常退出
// 2.线程池为空,但是队列中还有任务没执行,看addWoker方法对这种情况的处理
addWorker(
null
,
false
);
}
}
|
tryTerminate
processWorkerExit方法中会尝试调用tryTerminate来终止线程池。这个方法在任何可能导致线程池终止的动作后执行:比如减少wokerCount或SHUTDOWN状态下从队列中移除任务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
final
void
tryTerminate() {
for
(;;) {
int
c = ctl.get();
// 以下状态直接返回:
// 1.线程池还处于RUNNING状态
// 2.SHUTDOWN状态但是任务队列非空
// 3.runState >= TIDYING 线程池已经停止了或在停止了
if
(isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty()))
return
;
// 只能是以下情形会继续下面的逻辑:结束线程池。
// 1.SHUTDOWN状态,这时不再接受新任务而且任务队列也空了
// 2.STOP状态,当调用了shutdownNow方法
// workerCount不为0则还不能停止线程池,而且这时线程都处于空闲等待的状态
// 需要中断让线程“醒”过来,醒过来的线程才能继续处理shutdown的信号。
if
(workerCountOf(c) !=
0
) {
// Eligible to terminate
// runWoker方法中w.unlock就是为了可以被中断,getTask方法也处理了中断。
// ONLY_ONE:这里只需要中断1个线程去处理shutdown信号就可以了。
interruptIdleWorkers(ONLY_ONE);
return
;
}
final
ReentrantLock mainLock =
this
.mainLock;
mainLock.lock();
try
{
// 进入TIDYING状态
if
(ctl.compareAndSet(c, ctlOf(TIDYING,
0
))) {
try
{
// 子类重载:一些资源清理工作
terminated();
}
finally
{
// TERMINATED状态
ctl.set(ctlOf(TERMINATED,
0
));
// 继续awaitTermination
termination.signalAll();
}
return
;
}
}
finally
{
mainLock.unlock();
}
// else retry on failed CAS
}
}
|
shutdown和shutdownNow
shutdown这个方法会将runState置为SHUTDOWN,会终止所有空闲的线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
void
shutdown() {
final
ReentrantLock mainLock =
this
.mainLock;
mainLock.lock();
try
{
checkShutdownAccess();
// 线程池状态设为SHUTDOWN,如果已经至少是这个状态那么则直接返回
advanceRunState(SHUTDOWN);
// 注意这里是中断所有空闲的线程:runWorker中等待的线程被中断 → 进入processWorkerExit →
// tryTerminate方法中会保证队列中剩余的任务得到执行。
interruptIdleWorkers();
onShutdown();
// hook for ScheduledThreadPoolExecutor
}
finally
{
mainLock.unlock();
}
tryTerminate();
}
|
shutdownNow方法将runState置为STOP。和shutdown方法的区别,这个方法会终止所有的线程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
List<Runnable> shutdownNow() {
List<Runnable> tasks;
final
ReentrantLock mainLock =
this
.mainLock;
mainLock.lock();
try
{
checkShutdownAccess();
// STOP状态:不再接受新任务且不再执行队列中的任务。
advanceRunState(STOP);
// 中断所有线程
interruptWorkers();
// 返回队列中还没有被执行的任务。
tasks = drainQueue();
}
finally
{
mainLock.unlock();
}
tryTerminate();
return
tasks;
}
|
主要区别在于shutdown调用的是interruptIdleWorkers这个方法,而shutdownNow实际调用的是Worker类的interruptIfStarted方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
private
void
interruptIdleWorkers(
boolean
onlyOne) {
final
ReentrantLock mainLock =
this
.mainLock;
mainLock.lock();
try
{
for
(Worker w : workers) {
Thread t = w.thread;
// w.tryLock能获取到锁,说明该线程没有在运行,因为runWorker中执行任务会先lock,
// 因此保证了中断的肯定是空闲的线程。
if
(!t.isInterrupted() && w.tryLock()) {
try
{
t.interrupt();
}
catch
(SecurityException ignore) {
}
finally
{
w.unlock();
}
}
if
(onlyOne)
break
;
}
}
finally
{
mainLock.unlock();
}
}
void
interruptIfStarted() {
Thread t;
// 初始化时state == -1
if
(getState() >=
0
&& (t = thread) !=
null
&& !t.isInterrupted()) {
try
{
t.interrupt();
}
catch
(SecurityException ignore) {
}
}
}
|
6.线程池的关闭
- 如果执行“//1”处“executor.shutdown();”,其会先抛出RejectedExecutionExeption异常,然后随机输出前7个线程的线程名称。
- 如果执行“//2”中的“executor.shutdownNow();”,其会先抛出7个InterruptedException异常,然后抛出1个RejectedExecutionException异常。
- 原因解析:
- shutdown():调用了此方法,线程池的状态会从RUNNING状态过渡到SHUTDOWN状态,不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务,而是会抛出RejectedExecutionExeption异常,但是在异常被抛出之前,已经添加的任务会一直执行,直至完成。
- shutdownNow():调用此方法,线程池的状态会从RUNNING状态过渡到STOP状态,并且会遍历调用线程池中每个线程的interrupt()方法,在每个线程中执行的任务被sleep()所阻塞,并且会响应中断。于是处于阻塞的7个线程抛出InterruptedException异常,再次添加新线程时,仍然像shutdown()一样,会被拒绝,并抛出RejectedExecutionException异常。
7.线程池的合理配置
要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:
- 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
- 任务的优先级:高,中和低。
- 任务的执行时间:长,中和短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接。
任务性质不同的任务可以用不同规模的线程池分开处理。
CPU密集型任务配置尽可能小的线程,如配置Ncpu+1个线程的线程池,可以使得每个线程都在执行任务。
IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。
混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
当任务需要某种通过资源池来管理的资源时,例如数据库连接,那么线程池和资源池的大小将会互相影响。如果每个任务都需要一个数据库连接,那么连接池的大小就限制了线程池的大小。同样,当线程池中的任务是数据库连接的唯一使用者时,那么线程池的大小又将限制连接池的大小。
8.线程池的监控
通过线程池提供的参数进行监控:
- taskCount:线程池需要执行的任务数量。
- completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
- largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
- getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。
- getActiveCount:获取活动的线程数。
通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:protected void beforeExecute(Thread t, Runnable r) { }