线程创建
要了解关闭,首先要了解创建。
ThreadPoolExecutor
有几个关键的参数:
- corePoolSize:线程池中核心线程的数量。当提交一个任务时,线程池会新建一个线程来执行任务,直到当前线程数等于corePoolSize。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程,否则刚启动时,线程池的线程数为0。
- workQueue:用来保存等待执行的任务的阻塞队列。当提交任务时,如果线程数已经达到corePoolSize,任务会被放入阻塞队列里等待执行。
- maximumPoolSize:线程池中允许的最大线程数。线程池的阻塞队列满了之后,还有任务提交,如果当前的线程数小于maximumPoolSize,则会新建线程来执行任务。注意,如果workQueue使用的是无界队列,该参数也就没有什么效果了。
- keepAliveTime:线程空闲的时间。线程的创建和销毁是需要代价的。线程执行完任务且没有新任务可执行时,并不会立即销毁,而是继续存活一段时间。默认情况下,核心线程即使空闲也不会被回收,有参数可以设置让核心线程也参与回收。
- RejectedExecutionHandler:拒绝策略。当前阻塞队列已满,且线程数已达到最大线程数时,根据配置的策略决定如何处理任务。
ThreadPoolExecutor
提交新任务时,分以下几种情况:
- 当前已创建的线程数小于
核心线程
数,创建新线程执行任务。 -
当前已创建的线程数大于等于
核心线程
数:-
阻塞队列
未满,添加任务到阻塞队列
中,等待执行。 -
阻塞队列
已满:- 当前已创建的线程数小于
最大线程
数,创建新线程执行任务。 - 当前已创建的线程数等于
最大线程
数,执行拒绝策略。
- 当前已创建的线程数小于
-
线程复用
Thread跟Runnable是一一对应的,Thread.start实际上是调Runnable.run,当run方法执行完,Thread也就结束要回收了。那么ThreadPoolExecutor是如何做到线程复用:让一个Thread能执行多个任务(Runnable)的方法?
下面贴出提交任务
时的几段代码:
public class ThreadPoolExecutor extends AbstractExecutorService {
public void execute(Runnable command) {
......
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
......
}
通过提交任务入口,看到添加任务是在addWorker
方法内。
public class ThreadPoolExecutor extends AbstractExecutorService {
private boolean addWorker(Runnable firstTask, boolean core) {
......
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
.....
if (workerAdded) {
t.start();
workerStarted = true;
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
}
这里可以看到,线程池是在提交的Runnable
外面套一层Worker
。
public class ThreadPoolExecutor extends AbstractExecutorService {
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
}
final void runWorker(Worker w) {
Runnable task = w.firstTask;
w.firstTask = null;
...
try {
while (task != null || (task = getTask()) != null) {
...
task.run();
...
}
} finally {
processWorkerExit(w, completedAbruptly);
}
}
}
Worker
也实现了Runnable
方法,在启动(run
)时,会先把创建时传过来的Runnable
对象执行,执行完之后再循环去任务列表里获取其他未执行的任务(getTask
)。所以线程池里Thread
是跟Worker
一一对应,Worker
里再循环执行多个任务。
线程停止
ThreadPoolExecutor怎么停止当前正在执行中的线程?
回到本文的主题,从ThreadPoolExecutor的两个停止方法入手:
shutdown
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // ㈠
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN); // ㈡
interruptIdleWorkers(); // ㈢
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock(); // ㈣
}
tryTerminate(); // ㈤
}
其中的㈠
、㈣
两步通过锁保证线程安全。
第㈡
步将线程状态改为SHUTDOWN
。线程池共用以下五种状态:
- RUNNING:处于RUNNING状态的线程池能够接受新任务,以及对新添加的任务进行处理。
- SHUTDOWN:处于SHUTDOWN状态的线程池不可以接受新任务,但是已添加的任务(阻塞队列里的任务)仍然会被处理。
- STOP:处于STOP状态的线程池不接收新任务,不处理已添加的任务。
- TIDYING:当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。
- TERMINATED:线程池彻底终止的状态。
第㈢
步调用interruptIdleWorkers
方法中断空闲
中的Worker
。
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
这里可以看到,方法遍历所有的Worker
,获取锁之后,最终中断是通过Thread.interrupt
来实现的。
那这里是怎么判断线程是否空闲?
我们回到Worker
这个类。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
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;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
}
final void runWorker(Worker w) {
...
w.unlock(); // allow interrupts
...
try {
while (task != null || (task = getTask()) != null) {
w.lock();
...
try {
...
task.run();
...
} finally {
...
w.unlock();
}
}
...
} finally {
processWorkerExit(w, completedAbruptly);
}
}
Worker
继承了AbstractQueuedSynchronizer
,AbstractQueuedSynchronizer
是JUC
下很多类的父类,有个state
字段。Worker
加锁-释放锁的操作如下:
-
tryLock
->tryAcquire(1)
->compareAndSetState(0, 1)
将state
值从0
变成1
。 -
lock
->acquire(1)
->tryAcquire(1)
->compareAndSetState(0, 1)
将state
值从0
变成1
。 -
unlock
->release
->tryRelease
->setState(0)
将state
值设为0
。
Worker
启动后锁状态变化如下:
-
Worker
创建的时候,将state
初始化为-1
。 - 启动时:
runWorker
->w.unlock
将state
值设为0
,。 - 执行任务时:
getTask()
->w.lock
->task.run()
->w.unlock()
,在获取到任务之后将state
值设为1
,执行完任务之后将state
值设为0
。
通过以上可以得出state
值对应的线程状态:
- -1:初始状态,线程还没启动。
- 0:线程已执行完任务,还没执行新任务。(空闲,已释放锁)
- 1:线程正在执行任务。(繁忙,拥有锁)
再回到interruptIdleWorkers
方法, 可以看到在t.interrupt()
之前调用了w.tryLock()
,如果这里成功获取到锁,就表明当前线程是空闲的,但如果线程正在执行,w.tryLock()
申请不到锁,也就执行不到t.interrupt()
shutdownNow
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // ㈠
try {
checkShutdownAccess();
advanceRunState(STOP); // ㈡
interruptWorkers(); // ㈢
tasks = drainQueue(); // ㈣
} finally {
mainLock.unlock(); // ㈤
}
tryTerminate(); // ㈥
return tasks;
}
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
第㈡
步将线程状态改为STOP
。
第㈢
步调用interruptWorkers
方法中断所有Worker
(不仅仅是空闲的Worker)。
第㈣
步drainQueue
获取并清理所有等待执行(还未被线程执行)的任务。
再进入interruptWorkers
:
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
通过state > 0
确认当前线程已经启动(前面有提到,state
初始状态是-1
,线程启动之后在0
和1
之间来回切换),之后也是通过Thread.interrupt
来中断线程。
到这里,可以知道shutdown
和shutdownNow
的差别在于:
-
shutdown
:不再接收新任务,“已提交但未执行的任务”会继续被执行,中断的是空闲中的线程。 -
shutdownNow
:不再接收新任务,“已提交但未执行的任务”也不会再执行,中断所有线程。
shutdown
和shutdownNow
方法的最后一步都是tryTerminate
,将状态最终变成TERMINATED
。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
线程中断
interrupt
肯定会让线程结束吗?
回到shutdown
方法,runWorker
时,线程循环:“取任务 -> 加锁 -> 执行任务 -> 释放锁 -> 取任务 -> ...”,在释放锁
和加锁
中间有间隙,如果这时候shutdown
刚好获取到锁,执行了interrupt
,那线程是不是就结束了,如果该线程刚好是线程池最后一个线程,那是不是task里还未执行的任务就永远不会被执行了?
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);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
回答这个问题前,我们再看下以下代码会打印几遍“begin”和“end”。
Thread thread = new Thread(() -> {
int count = 2;
for(int i = 0; i < count; i++) {
System.out.println("begin");
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception ignore) {
}
System.out.println("end");
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
thread.join();
答案是两遍。说明线程被interrupt
后并不一定就会结束。
进入Thread
的interrupt
方法源码,方法上注解说明:
-
sleep
,wait
,join
等方法,会抛出InterruptedException
(注意这里并没有设置中断状态)。 -
InterruptibleChannel
的I/O方法将会抛出ClosedByInterruptException
,并设置中断状态。 -
Selector
阻塞被中断,会设置中断状态,并回到selection operation
。 - 如果前面的条件都不成立,那么这个线程的中断将设置状态。
可以看到,除了特定的方法之外,interrupt
仅仅是将该线程的中断状态
设置为true
,并不会影响任务的执行。
比如以下代码,thread.join()
会被一直阻塞到数据库sleep
60秒执行完。
Thread thread = new Thread(() -> {
// 数据库执行'select sleep(60)'
db.sleep60();
});
thread.start();
TimeUnit.SECONDS.sleep(5);
thread.interrupt();
thread.join();
interrupt
并不是粗暴的停掉线程,而是友好地告诉线程:“在方便且愿意的时候停止当前正在做的事”。
这时候我们回到前面的问题,即使shutdown
刚好在unlock-lock
的间隙获取到了锁并执行了interrupt
,也不影响线程继续执行(getTask
方法里自己内部有捕获了InterruptedException
)。
最后我们来看下下面这段代码有什么问题?
class MyRunnable implements Runnable {
@Override
public void run() {
while (true) {
try {
// 业务代码
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
}
}
}
这段代码的问题是:这个线程永远不会被停掉,可能会影响到项目/服务的正常停止。
可以改成以下方式:
class MyRunnable implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
// 业务代码
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
以上源码基于JDK1.8。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。