多线程是开发常用的技术,Android 开发更是对UI 线程进行了限制,耗时操作必须放到子线程,new Thread 很简单就创建了一个子线程,但如果大型项目,很多地方都new Thread ,那么有一个问题就是我们将无法管理我的线程,大量的创建线程,占用过多资源,而且它们之间也会相互竞争,还有就是创建线程与线程执行完的销毁操作都是一部分系统开销。所以引入线程池很有意义。
描述
线程池的优点
1. 若重用了已存在线程,则节省了线程创建和销毁所带来的的开销;
2. 若重用了已存在线程,则提高响应速度,省去了新线程创建的时间;
3. 方便线程并发数的管控,例如提供了shutdown 方法;
4. 功能更强大,例如有的提供了定时等功能,使用方便简单;
线程池参数
ThreadPoolExecutor 的几个参数及意义如下:
/**
* @param corePoolSize 线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态。
* @param maximumPoolSize 线程池所能容纳的最大线程数,当活动线程数达到这个数值后,再提交的任务将会走拒绝策略。
* @param keepAliveTime 非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收,
* 当allowCoreThreadTimeOut 属性设置为true 时,keepAliveTime 同样会作用于核心线程。
* @param unit 用于指定keepAliveTime 参数的时间单位,这是一个枚举。
* @param workQueue 线程池中的任务队列,当核心线程满了,再提交的任务会先考虑进入这个队列
* @param threadFactory 线程工厂,为线程池提供创建新线程的功能。
* @param handler 当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者是无法成功执行任务,
* 这时候通过该对象走饱和策略。默认是AbortPolicy,在无法处理新任务时抛出RejectedExecutionException异常。
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
这里再说一下以下两个参数,
workQueue :对于该参数可以使用下面几个阻塞队列,
ArrayBlockingQueue 基于数组实现的有界的阻塞队列(FIFO)
LinkedBlockingQueue 基于链表实现的阻塞队列(FIFO)
SynchronousQueue 内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间。
对于SynchronousQueue中的数据元素只有当我们试着取走的时候才可能存在。
PriorityBlockingQueue 具有优先级的无限阻塞队列。
还能够通过实现BlockingQueue 接口来自定义我们所需要的阻塞队列。
handler :默认是AbortPolicy ,还提供的四个可选值,
CallerRunsPolicy 只用调用者所在线程来运行任务。
AbortPolicy 直接抛出RejectedExecutionException异常。
DiscardPolicy 丢弃掉该任务,不进行处理。
DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务。
还可以通过实现RejectedExecutionHandler 接口来自定义我们自己的handler。如记录日志或持久化不能处理的任务。
线程池执行流程
就提交的任务,先让核心线程处理,核心线程满了就进入队列,队列满了就创建非核心线程执行,达到最大线程数量,就走饱和策略;
四种线程池
Java 提供了四种具有不同功能的线程池,使用好这四种,就可以满足大部分功能场景了,它们都是直接或者间接配置ThreadPoolExecutor 来实现各自的功能。
newFixedThreadPool
线程数量固定的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
只有核心线程,即使线程空闲,也不会被回收,没有超时机制,只有关闭线程池才会回收这些线程,由于线程不被回收,所以响应更加快速,(节省了线程加载的时间,相当于有预加载),队列使用LinkedBlockingQueue ,没有大小限制。
newCachedThreadPool
核心线程数为0, 线程池的最大线程数Integer.MAX_VALUE
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
当线程池中的线程都处于活动状态的时候,线程池就会创建一个新的线程来处理任务。该线程池中的线程超时时长为60秒,所以当线程处于闲置状态超过60秒的时候便会被回收。意味着若是整个线程池的线程都处于闲置状态超过60秒以后,在这种线程池里是不存在任何线程的,所以这时候几乎不占用任何的系统资源。newCachedThreadPool 的任务队列是SynchronousQueue ,所以说在线程池里如果没有空闲线程,将会创建新的线程来执行任务。
newScheduledThreadPool
核心线程数是固定的,对于非核心线程几乎可以说是没有限制的,并且当非核心线程处于限制状态的时候就会立即被回收。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
简单使用如下:
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"延迟2秒执行");
}
}, 2, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"延迟2秒后每隔1秒执行");
}
}, 2, 1, TimeUnit.SECONDS);
简单说一下,它的两个方法:
scheduleAtFixedRate() 的period 参数是上一个任务开始到下一个任务开始的间隔,是连续执行之间的一段时间;
scheduleWithFixedDelay() 的delay 参数是上一个任务结束到下一个任务开始的间隔,是终止之间的延迟;
newSingleThreadScheduledExecutor
可见它就是ScheduledThreadPoolExecutor 的,只不过核心线程数是1
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
newSingleThreadExecutor
该线程池只有一个核心线程,对于任务队列没有大小限制,也就意味着这一个任务处于活动状态时,其他任务都会在任务队列中排队等候依次执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
源码分析
对ThreadPoolExecutor 的部分方法进行了分析,从而使我们更清晰线程池的原理。
在ThreadPoolExecutor 的属性定义里用了很多位运算来表示线程池的状态,来看下,
/**
* 这里32位Integer ,高3位表示线程池状态,低29位表示工作线程数
*/
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/**
* 线程个数位数,表示的Integer 除去最高的3位之后剩下的位数表示线程个数
*/
private static final int COUNT_BITS = Integer.SIZE - 3;
/**
* 容量,例如00011111111111111111111111111111
*/
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/**
* 接受新任务并且处理阻塞队列里面任务,11100000000000000000000000000000
*/
private static final int RUNNING = -1 << COUNT_BITS;
/**
* 拒绝新任务但是处理阻塞队列的任务,00000000000000000000000000000000
*/
private static final int SHUTDOWN = 0 << COUNT_BITS;
/**
* 拒接新任务并且抛弃阻塞队列里面的任务,同时会中断正在处理的任务,00100000000000000000000000000000
*/
private static final int STOP = 1 << COUNT_BITS;
/**
* 表示所以任务已经终止,01000000000000000000000000000000
*/
private static final int TIDYING = 2 << COUNT_BITS;
/**
* 终止状态,01100000000000000000000000000000
*/
private static final int TERMINATED = 3 << COUNT_BITS;
/**
* 获取高3位,获取线程池状态
*/
private static int runStateOf(int c) { return c & ~CAPACITY; }
/**
* 获取低29位,获取线程池线程的数量
*/
private static int workerCountOf(int c) { return c & CAPACITY; }
/**
* 组合ctl变量,rs为线程池状态,wc为线程池线程数量
*/
private static int ctlOf(int rs, int wc) { return rs | wc; }
来看一下ThreadPoolExecutor的execute 方法:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取线程池状态与线程池数量
int c = ctl.get();
// 如果线程池的数量小于corePoolSize,则新建线程执行任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 添加失败,获取最新的状态
c = ctl.get();
}
// 达到核心线程数,并且线程池状态是RUNNING ,则将任务入队
if (isRunning(c) && workQueue.offer(command)) {
// 再次检查状态与线程数
int recheck = ctl.get();
// 不是RUNNING状态,从队列移除当前任务,并且执行饱和策略。
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果线程池的线程数量为0,添加一个新的线程。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 当然线程池状态不是RUNNING 了,也会执行这里
// 如果队列满的会执行这里,新建线程也就是所说的非核心线程
else if (!addWorker(command, false))
// 如果达到maximumPoolSize ,会执行饱和策略
reject(command);
}
下面分析addWorker 方法的源码,
private boolean addWorker(Runnable firstTask, boolean core) {
// 不需要预定义的语法标签,可用于快速退出多层嵌套循环
retry:
for (;;) {
int c = ctl.get();
//获取线程池的状态
int rs = runStateOf(c);
// 仅在必要时检查队列是否为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; // 检查状态,看此时线程池还能否添加Worker ,不能则退出
// CAS 设置线程计数
for (;;) {
int wc = workerCountOf(c);
// 达到容量,直接返回
if (wc >= CAPACITY ||
// 这里根据core 来决定跟corePoolSize 比较,还是maximumPoolSize
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// CAS 增加计数
if (compareAndIncrementWorkerCount(c))
break retry; // 增加成功则跳出
// 增加失败,则重新获取状态与数量
c = ctl.get(); // Re-read ctl
// 如果新状态与原状态不一样了,那就跳到外层循环重新走之前的逻辑,
// 如果没变则只用重新走内层循环逻辑即可
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 工作线程启动标志
boolean workerStarted = false;
// 工作线程添加成功标志
boolean workerAdded = false;
Worker w = null;
try {
// 新建Worker ,这里的这个Worker 可以先理解为工作线程,后面会分析它的结构
w = new Worker(firstTask);
// 获取worker里的线程
final Thread t = w.thread;
//判空
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 加锁,workers 是一个HashSet<Worker> 类型,对该集合的操作要加锁
mainLock.lock();
try {
// 重新获取状态
int rs = runStateOf(ctl.get());
// 判断状态,只有状态没问题,才能执行添加操作
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// //添加worker 到集合
workers.add(w);
int s = workers.size();
// 是否刷新largestPoolSize
if (s > largestPoolSize)
largestPoolSize = s;
// //添加worker 成功,设置标志位
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
// 线程启动,设置相应标志位
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
可以看出addWorker 方法,大体分为两部分,第一部分使用CAS 增加线程数计数,第二部分是添加工作线程到集合,并调用了start 方法启动线程,当然也包括添加失败的应对策略。
可以看出不管是execute 方法,还是addWorker 方法,都少不了状态的检查,只有状态检查通过才能进行相应的处理。五种状态的十进制值从小到依次是RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED ,这样就可以通过比较大小来确定线程池的状态。例如isRunning 方法,
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
前面说Worker 是工作线程,那么它究竟啥样呢,为什么这么说呢?我们来看一下,
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
// 执行任务的线程,通过ThreadFactory 创建
final Thread thread;
// 第一个任务
Runnable firstTask;
// 每个线程完成任务的数量
volatile long completedTasks;
// 从ThreadFactory 创建线程,设置任务
Worker(Runnable firstTask) {
// 置为-1 ,禁止中断
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
// 可见真正逻辑在runWorker 方法,后面分析该方法
public void run() {
runWorker(this);
}
// 锁的相应方法
//
// 值0表示解锁状态。
// 值1表示锁定状态。
// 是否持有独占锁
protected boolean isHeldExclusively() {
return getState() != 0;
}
// AQS 相关方法在,调用acquire 方法会调用到该方法
protected boolean tryAcquire(int unused) {
// CAS 操作
if (compareAndSetState(0, 1)) {
// 成功了设置当前线程独占,并返回true
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// AQS 相关方法在,release 方法会调用到该方法
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(); }
// 主要为调用Worker 对应thread 的interrupt 方法
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
这里发现Worker 实现了AQS ,是一个不可重入的独占锁模式,并且还集成了Runnable 接口;这里值得说两点,
1. 可以看到构建线程的时候,传入参数this ,而this 就是当前的Worker 啊,所以调用线程的start 方法会调用线程的Runnable 的run 也就是,调用Worker 的run 方法,在Worker 的run 方法,又调用到runWorker 方法,这里先简单说一下,runWorker 方法里会调用到firstTask 的run 方法;
2. 就关于worker 的这些锁的相应方法,这里不是起的同步作用,因为使用线程池本来就是要的高并发,怎么会加锁同步呢?而且每个线程有用各自的锁也根本起不到线程间的同步啊;那为什么还要实现AQS 呢?这里要说到关闭线程的两个方法shutdow 与shutdownNow ,前者关闭线程池,但允许当前运行线程运行完,只是不能再提交任务了,但还是会把已有任务运行完,然后在走关闭流程,后者从名字上来看就是现在就要关闭,即直接走关闭流程。这样如何区分线程有没有在执行任务呢?就是锁,尝试获取锁获取不到,说明在执行;
接下来我们看一下runWorker方法,
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 这里还记的我们在Worker构造函数里将state置为-1,这里将state设置为0 ,允许中断
w.unlock(); // allow interrupts
// 一个判断以下代码是否按步骤正常走的标志位,因为如果有突发情况要处理
boolean completedAbruptly = true;
try {
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 {
// 任务执行,即调用相应Runnable 的run 方法
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;
// 每个Worker 完成的任务的计数
w.completedTasks++;
// 解锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
//处理Worker的退出操作
processWorkerExit(w, completedAbruptly);
}
}
首先要明确这里是调用了工作线程的start 方法后才会执行到了,这里已经是在子线程执行了,所以在子线程里调用的task.run() 自然也是在子线程运行的。
可见有一个while 循环,当前任务执行完了,会再次调用getTask 方法去获取一个任务,知道取不到,task 为空结束循环,最后会调用到processWorkerExit 方法,回收处理在该方法里;
还有这里的加锁不是为了同步(每个线程使用各自的锁,也没法同步)前面已经说过了,而是关闭线程的时候会用到,之后分析shutdown 与shutdownNow 的时候,一对比就清楚了;
接下来看getTask 方法,
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取线程数
int wc = workerCountOf(c);
// allowCoreThreadTimeOut 为true 或者wc > corePoolSize 会执行超市回收策略
// 1.allowCoreThreadTimeOut 为true 则核心线程都要考虑走回收策略
// 2.wc > corePoolSize 则说明有非核心线程,那么非核心线程考虑走回收策略
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 这里意味着线程数大于corePoolSize,或指定了allowCoreThreadTimeOut为true,
// 并且线程数大于1 或队列为空,尝试对线程池的数量进行减少操作,然后返回null
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
// 获取一段时间如果没有获取到返回null,之后应该会执行回收策略
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
// 这里不会回收执行回收策略不会走这里,会如果队列为空会阻塞指导获取到
workQueue.take();
//如果从队列获取到内容则返回
if (r != null)
return r;
//如果没有获取到,超时了,则设置timeOut状态
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
可见该方法会对非核心线程考虑走回收,会对设置allowCoreThreadTimeOut 为true 的所有线程考虑走回收,会调用任务队列的poll 方法,获取一段时间获取不到的话,就会返回null ,然后getTask 方法也就会返回null 了,者也就会执行到processWorkerExit 方法,前面说过回收逻辑在这里,满足了应该回收的条件就会回收那个工作线程了,下面我们来看一下,
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 这个变量之前提到过,突发情况会置为true
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
// 减少线程数量1
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); // 对workers 集合的处理要加锁
try {
// 计数
completedTaskCount += w.completedTasks;
// 移除Worker
workers.remove(w);
} finally {
mainLock.unlock(); // 解锁
}
// 尝试设置线程池状态为TERMINATED
tryTerminate();
int c = ctl.get();
// 如果线程池的状态小于STOP,也就是SHUTDOWN或RUNNING状态
if (runStateLessThan(c, STOP)) {
// 如果不是突发情况,也就是正常结束
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 最小值为0 ,并且任务队列为不为空
if (min == 0 && ! workQueue.isEmpty())
min = 1; // 任务队列不为空说明还有任务要执行,所以讲最小值调成了1
// 当前线程不低于最小值,就返回了,不然还要添加一个线程执行任务
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 这里添加了一个工作线程
addWorker(null, false);
}
}
可见会先移除Worker ,然后默认情况会判断当前工作线程有没有达到corePoolSize ,没有达到,且任务队列不为空,会创建线程使线程池的线程数维持核心线程数,若是设置allowCoreThreadTimeOut 为true 的情况,任务队列不为空,且当前线程一个也没有,会创建一个线程来执行。
线程池关闭
调用线程池的shutdown()或shutdownNow() 来关闭线程池;
shutdown 方法对线程池状态进行SHUTDOWN设置,然后中断当前没有执行任务的线程,
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
// 加锁,因为要操作workers
mainLock.lock();
try {
// 检查权限
checkShutdownAccess();
// 设置线程池状态为SHUTDOWN,如果状态已经是 >= SHUTDOWN 则直接返回
advanceRunState(SHUTDOWN);
// 中断闲置的线程,即当前没有运行的
interruptIdleWorkers();
// 一个空方法,如果用户想在shutdown 时做点操作可以实现内容
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock(); // 解锁
}
tryTerminate();
}
看一下interruptIdleWorkers 方法,看看是如何只中断未运行线程的,
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
// 加锁,与之前一样这里对workers 集合进行了操作,所以要保证线程安全
mainLock.lock();
try {
// 遍历集合
for (Worker w : workers) {
Thread t = w.thread;
// 如果线程没有设置中断标志,并且可以获取到Worker 自己的锁
if (!t.isInterrupted() && w.tryLock()) {
try {
// 设置中断标志
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock(); // 解锁
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock(); // 解锁
}
}
可见,遍历集合,这里就用的了Worker 自己的锁,也就是前面所说的那个不起同步作用的锁,而只是由于执行任务获取锁了,而到 方法在走tryLock 方法的时候就返回false 了,这样就使得运行的线程不会被中断,这样中断的只是未执行的线程。
shutdownNow 方法方法对线程池状态进行STOP设置,然后中断所有线程(包括正在执行的),并返回未执行任务的集合,
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); // 检查权限
// 设置线程池状态为STOP,如果状态已经是 >= STOP 则直接返回
advanceRunState(STOP);
// 进行中断线程,运行的也要进行中断
interruptWorkers();
// 未完成的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
看一下interruptWorkers 方法就清楚了,
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍历集合,直接进行中断操作
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
// 进行判断,然后就是调用线程的interrupt 方法
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}