概念
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程在执行过程中拥有独立的内存单元,而多个线程共享内存。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间。
使用线程池的好处
i. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
ii. 提高响应速度。当任务到达时,不需要等到线程创建就能立即执行。
iii. 提高线程的可管理性。使用线程池可以进行统一的分配,调优和监控。
线程的创建与管理
i. 通过继承Thread类来创建一个线程
ii. Runnable是个接口,实现该接口并重写run方法,new Thread(runnable).start(),线程启动时就会自动调用该对象的run方法
iii. Callable是个接口,实现call()方法,使用FutureTask类来包装Callable对象,使用 FutureTask对象作为Thread对象的target创建并启动新线程;也可使用线程池启动
a. Runnable和Callable的区别是: (1)Callable规定的方法是call(),Runnable规定的方法 是run(). (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得。 (3)call方法可以抛出异常,run方法不可以。 (4)运行Callable任务可以拿到一个Future 对象,Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算 的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如果线程没有 执行完,Future.get()方法可能会阻塞当前线程的执行;如果线程出现异常, Future.get()会throws InterruptedException或者ExecutionException;如果线程已 经取消,会跑出CancellationException。取消由cancel 方法来执行。isDone确定任务 是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使 用 Future 但又不提供可用的结果,则可以声明Future> 形式类型、并返回 null 作 为底层任务的结果。
iv. 线程池:Executors 类提供了方便的工厂方法来创建不同类型的 executor services 。无论 是Runnable接口的实现类还是Callable接口的实现类,都可以被ThreadPoolExecutor或 ScheduledThreadPoolExecutor执行
a. public static ExecutorService newCachedThreadPool() 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。
b. public static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,超出的线程会在队列中等待。
c. public static ExecutorService newSingleThreadExecutor() 创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,它只会用唯一的工作线程来 执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
d. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 创建一个定长线程池,支持定时及周期性任务执行。
e. newWorkStealingPool它是新的线程池类ForkJoinPool的扩展,由于能够合理的使用 CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中
v. ForkJoinPool 的每个工作线程都维护着一个工作队列(WorkQueue),这是一个双端队列 (Deque),里面存放的对象是任务(ForkJoinTask)。
a. 每个工作线程在运行中产生新的任务(通常是因为调用了 fork())时,会放入工作队列的队尾,并且工作线程在处理自己的工作队列时,使用的是 LIFO 方式,也就是说每次从队尾取出任务来执行。
b. 每个工作线程在处理自己的工作队列同时,会尝试窃取一个任务(或是来自于刚刚提交到 pool 的任务,或是来自于其他工作线程的工作队列),窃取的任务位于其他线程的工作队列的队首,也就是说工作线程在窃取其他工作线程的任务时,使用的是 FIFO 方 式。
c. 在遇到 join() 时,如果需要 join 的任务尚未完成,则会先处理其他任务,并等待其完成。
d. 在既没有自己的任务,也没有可以窃取的任务时,进入休眠。
vi. ExecutorCompletionService:内部管理者一个已完成任务的阻塞队列,如果阻塞队列中有已完成的任务, take方法就返回任务的结果, 否则阻塞等待任务完成,基于FutureTask实现。
a. AbortPolicy:直接抛出异常,这是默认策略;
b. CallerRunsPolicy:用调用者所在的线程来执行任务;
c. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
d. DiscardPolicy:直接丢弃任务;
1. 线程池执行源码
/*
* ThreadPoolExecutor的execute方法
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// clt 记录着 runState workerCount
int c = ctl.get();
/*
* workerCountOf 方法取出低29位的值,表示当前活动的线程数
*/
if (workerCountOf(c) < corePoolSize) {
/*
* addWorker 方法的主要工作是在线程池中创建一个新的线程并执行
* firstTask 参数,用于指定新增的线程执行的第一个任务
* core 参数为
* true 表示在新增线程时会判断当前活动线程数是否少于corePoolSize
* false 表示在新增线程前需要判断当前活动线程数是否少于maximumPoolSize
*/
if (addWorker(command, true))
return;
/*
* 如果添加失败,则重新获取ctl值
*/
c = ctl.get();
}
/*
* 如果当前线程池是运行状态并且任务添加到队列成功
*/
if (isRunning(c) && workQueue.offer(command)) {
// 重新获取ctl值
int recheck = ctl.get();
// 再次判断线程池的运行状态,如果不是运行状态,由于之前已经把command添加到workQueue中了
// 这时需要移除该command
// 执行过后通过handler使用拒绝策略对该任务进行处理,整个方法返回
if (! isRunning(recheck) && remove(command))
reject(command);
/*
* 获取线程池中的有效线程数,如果数量是0,则执行addWorker方法
* 这里传入的参数表示:
* 1. 第一个参数为null,表示在线程池中创建一个线程,但不去启动;
* 2. 第二个参数为false,将线程池的有限线程数量的上限设置为maximumPoolSize,添加线程时根据maximumPoolSize来判断;
* 如果判断workerCount大于0,则直接返回,在workQueue中新增的command会在将来的某个时刻被执行。
*/
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
/*
* 如果执行到这里,有两种情况:
* 1. 线程池已经不是 RUNNING 状态;
* 2. 线程池是 RUNNING 状态,但 workerCount >= corePoolSize 并且 workQueue 已满。
* 这时,再次调用addWorker方法,但第二个参数传入为false,将线程池的有限线程数量的上限设置为 maxmumPoolSize;
* 如果失败则拒绝该任务
*/
else if (!addWorker(command, false))
reject(command);
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
// 获取运行状态
int rs = runStateOf(c);
/*
* 这个if判断
* 如果rs >= SHUTDOWN,则表示此时不再接收新任务;
* 接着判断以下3个条件,只要有1个不满足,则返回 false;
* 1. rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
* 2. firstTask为空
* 3. 阻塞队列不为空
*
* 首先考虑 rs == SHUTDOWN 的情况
* 这种情况下不会接受新提交的任务,所以在firstTask不为空的时候会返回false;
* 然后,如果firstTask为空,并且workQueue也为空,则返回false,
* 因为队列中已经没有任务了,不需要再添加线程了
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 获取线程数
int wc = workerCountOf(c);
// 如果wc超过CAPACITY,也就是ctl的低29位的最大值(二进制是29个1),返回false;
// 这里的core是addWorker方法的第二个参数,如果为true表示根据corePoolSize来比较
// 如果为false则根据maximumPoolSize来比较
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 尝试增加workerCount,如果成功,则跳出第一个for循环
if (compareAndIncrementWorkerCount(c))
break retry;
// 如果增加workerCount失败,则重新获取ctl的值
c = ctl.get(); // Re-read ctl
// 如果当前的运行状态不等于rs,说明状态已被改变,返回第一个for循环继续执行
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 {
// 根据firstTask来创建worker对象
w = new Worker(firstTask);
// 每一个worker对象都会创建一个线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN 表示是 RUNNING 状态
// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,向线程池中添加线程。
// 因为在SHUTDOWN时不会再添加新的任务,但还是会执行workQueue中的任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// workers是一个hashset
workers.add(w);
int s = workers.size();
// largestPoolSize 记录着线程池中出现过的最大线程数量
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 启动线程
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
2. 重点
当allowCoreThreadTimeOut=true或者此时工作线程大于corePoolSize时,线程调用 BlockingQueue的poll方法获取任务,若超过keepAliveTime时间,则返回null, timedOut=true,则getTask会返回null,线程中的runWorker方法会退出while循环,线程接下来会被回收。
// ThreadPoolExecutor的getTask方法
private Runnable getTask() {
// timeout 变量的值表示上次从阻塞队列中取任务时是否超时
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.
/*
* 如果线程池状态 rs >= SHUTDOWN,也就是非RUNNING状态,再进行以下判断:
* 1. rs >= STOP,线程池是否正在stop;
* 2. 阻塞队列是否为空。
* 如果以上条件满足,则将workerCount减1并返回null
* 因为如果当前线程池状态的值是SHUTDOWN或以上时,不允许再向阻塞队列中添加任务
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
// timed 变量用于判断是否需要进行超时控制
// allowCoreThreadTimeOut 默认是false,也就是核心线程不允许进行超时
// wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量
// 对于超过核心线程数量的这些线程,需要进行超时控制
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/*
* wc > maxmumPoolSize 的情况是因为可能在此方法执行阶段同时执行了setMaximumPoolSize方法;
* timed & timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中获取任务发生了超时
* 接下来判断,如果有效线程数量大于1,或者阻塞队列是空的,那么尝试将workerCount减一;
* 如果减一失败,则返回重试。
* 如果 wc == 1时,也就说明当前线程是线程池中唯一的一个线程了。
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
/*
* 保证核心线程不被销毁
* 根据timed来判断,如果为true,则通过阻塞队列的poll方法进行超时控制,如果在keepAliveTime 时间内没有获取到任务,则返回null;
* 否则通过take方法,如果这时队列为空,则take方法会阻塞直到队列不为空。
*/
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
// 如果 r == null,说明已经超时,timeOut设置为true
timedOut = true;
} catch (InterruptedException retry) {
// 如果获取任务时当前线程发生了中断,则设置timeOut为false并返回循环重试
timedOut = false;
}
}
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 计算等待截止时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
// 1. 判断阻塞线程是否被中断,如果被中断则在等待队列中删除该节点并抛出InterruptedException异常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
// 2. 获取当前状态,如果状态大于COMPLETING
// 说明任务已经结束(要么正常结束,要么异常结束,要么被取消)
// 则把thread显示置空,并返回结果
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 3. 如果状态处于中间状态COMPLETING
// 表示任务已经结束但是任务执行线程还没来得及给outcome赋值
// 这个时候让出执行权让其他线程优先执行
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 4. 如果等待节点为空,则构造一个等待节点
else if (q == null)
q = new WaitNode();
// 5. 如果还没有入队列,则把当前节点加入waiters首节点并替换原来waiters
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
// 如果需要等待特定时间,则先计算要等待的时间
// 如果已经超时,则删除对应节点并返回对应的状态
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 6. 阻塞等待特定时间
LockSupport.parkNanos(this, nanos);
}
else
// 6. 阻塞等地直到被其他线程唤醒
LockSupport.park(this);
}
}
不管是任务执行异常还是任务正常执行完毕,或者取消任务,最后都会调用finishCompletion()方法。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:
ThreadPoolExecutor提供了两个方法,用于线程池的关闭
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
在前面我们多次提到了任务缓存队列,即workQueue,它用来存放等待执行的任务。 BlockingQueue 是个接口,你需要使用它的实现之一来使用BlockingQueue,java.util.concurrent 包下具有以下 BlockingQueue 接口的实现类:
Java线程既是工作单元,也是执行机制。从JDK 5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供。
可以这么理解,应用程序通过Executor框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
Executor框架主要由3大部分组成:
Executor框架的主要成员:
ThreadPoolExecutor
ThreadPoolExecutor通常使用工厂类Executors来创建,Executors可以创建3种类型的 ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool、CachedThreadPool。
下面分别介绍这3种ThreadPoolExecutor。
1)FixedThreadPool。下面是Executors提供的,创建使用固定线程数的FixedThreadPool的API
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory
threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。
通过ThreadPoolExecutor的构造方法可以看出,工厂类传递的第一个参数和第二个参数都设置成了 nThreads。即线程池的初始值和最大值都设置为了指定的线程数量。 keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程仍然没有可以执行的任务将会被终止,这里设置成0L表示多余的空闲线程会被立即终止。
通过构造参数可以看出,线程池的构造参数中还传入了一个阻塞队列,该阻塞队列的作用其实就是作为一个任务的缓冲区。
现在可以分析一下该线程池的工作流程如下:
所以外部线程可以一直提交任务到线程池中阻塞队列中,然后,线程池中的工作线程在获取任务进行执行,这里其实是一个典型的生产者-消费者模式。
但是外部线程提交的任务数量有限制么,这就要看传入的阻塞队列是否有容量限制,从源码可以看出 是传入的一个没有指定构造参数的LinkedBlockingQueue,发现这里没有指定容量的大小,可以该队列的构造方法实现中去查看源码如下:
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
发现没有传入任务参数的时候,队列会默认创建容量大小为Integer.MAX_VALUE的阻塞队列。
2)SingleThreadExecutor。下面是Executors提供的,创建使用单个线程的 SingleThreadExecutor的API
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
SingleThreadExecutor适用于需要保证顺序执行各个任务;并且在任意时间点,不会有多个线程是活动着的应用场景。
SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1。其他参数与 FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的 工作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor使用无界队列作为工作队列对线程池带来的影响与FixedThreadPool相同。
执行流程如下:
3)CachedThreadPool。下面是Executors提供的,创建一个会根据需要而创建新线程的 CachedThreadPool的API
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
CachedThreadPool是大小无界的线程池,适用于执行很多短期异步任务的小程序,或者是负载比较轻的服务器。
CachedThreadPool的corePoolSize被设置为0,即corePool为空;maximumPoolSize被设置为 Integer.MAX_VALUE,即maximumPool是无界的。这里把keepAliveTime设置为60L,意味着 CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作 队列。 CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但 CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于 maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下, CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
执行流程如下:
SynchronousQueue是一个没有容量的阻塞队列。 每个插入操作必须等待另一个线程的对应移除操 作,反之亦然。CachedThreadPool使用SynchronousQueue,把主线程提交的任务传递给空闲线 程执行。
SynchronousQueue是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。
ScheduledThreadPoolExecutor
带着BAT大厂的面试问题理解ScheduledThreadPoolExecutor:
ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor,为任务提供延迟或周期执行,属于线程池的一种。和 ThreadPoolExecutor 相比,它还具有以下几种特性:
ScheduledThreadPoolExecutor继承自 ThreadPoolExecutor,ScheduledThreadPoolExecutor 内部构造了两个内部类 ScheduledFutureTask 和 DelayedWorkQueue:
内部类ScheduledFutureTask
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
// 为相同延时任务提供的顺序编号
private final long sequenceNumber;
// 任务可以执行的时间,纳秒级
private long time;
// 重复任务的执行周期时间,纳秒级
private final long period;
// 重新入队的任务
RunnableScheduledFuture<V> outerTask = this;
// 延迟队列的索引,以支持更快的取消操作
int heapIndex;
}
核心方法run()
public void run() {
boolean periodic = isPeriodic(); // 是否为周期任务
if (!canRunInCurrentRunState(periodic)) // 当前状态是否可以执行
cancel(false);
else if (!periodic)
// 不是周期任务,可以执行
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) {
setNextRunTime(); // 设置下一次运行时间
reExecutePeriodic(outerTask); // 重排序下一个周期任务
}
}
ScheduledFutureTask 的run方法重写了 FutureTask 的版本,以便执行周期任务时重置/重排序任务。任务的执行通过父类 FutureTask 的run实现。内部有两个针对周期任务的方法:
// 设置下一次执行任务的时间
private void setNextRunTime() {
long p = period;
if (p > 0) // 固定速率执行,scheduleAtFixedRate
time += p;
else
time = triggerTime(-p); // 固定延迟执行,scheduleWithFixedDelay
}
// 计算固定延迟任务的执行时间
private long triggerTime(long delay, TimeUnit unit) {
return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));
}
// 重排序一个周期任务
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) { // 池关闭后可继续执行
super.getQueue().add(task); // 任务入列
// 重新检查run‐after‐shutdown参数,如果不能继续运行就移除队列任务,并取消任务的执行
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart(); // 启动一个新的线程等待任务
}
}
reExecutePeriodic与delayedExecute的执行策略一致,只不过reExecutePeriodic不会执行拒绝策略而是直接丢掉任务。
cancel方法
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = super.cancel(mayInterruptIfRunning);
if (cancelled && removeOnCancel && heapIndex >= 0)
remove(this);
return cancelled;
}
ScheduledFutureTask.cancel本质上由其父类 FutureTask.cancel 实现。取消任务成功后会根据 removeOnCancel参数决定是否从队列中移除此任务。
核心属性
// 关闭后继续执行已经存在的周期任务
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
// 关闭后继续执行已经存在的延时任务
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
// 取消任务后移除
private volatile boolean removeOnCancel = false;
// 为相同延时的任务提供的顺序编号,保证任务之间的FIFO顺序
private static final AtomicLong sequencer = new AtomicLong();
构造函数
看下构造函数,ScheduledThreadPoolExecutor 内部有四个构造函数,这里我们只看这个最大构造灵活度的:
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
构造函数都是通过super调用了ThreadPoolExecutor的构造,并且使用特定等待队列 DelayedWorkQueue
核心方法:Schedule
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit))); // 构造ScheduledFutureTask任务
delayedExecute(t); // 任务执行主方法
return t;
}
schedule主要用于执行一次性(延迟)任务。函数执行逻辑分两步:
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task); // 池已关闭,执行拒绝策略
else {
super.getQueue().add(task); // 任务入队
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) && // 判断run‐after‐shutdown参数
remove(task)) // 移除任务
task.cancel(false);
else
ensurePrestart(); // 启动一个新的线程等待任务
}
}
delayedExecute是执行任务的主方法,方法执行逻辑如下:
A: 如果池正在运行,或者 run-after-shutdown 参数值为true,则调用父类方法ensurePrestart启动一个新的线程等待执行任务。ensurePrestart源码如下:
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
ensurePrestart是父类 ThreadPoolExecutor 的方法,用于启动一个新的工作线程等待执行任务,即使corePoolSize为0也会安排一个新线程。
B: 如果池已经关闭,并且 run-after-shutdown 参数值为false,则执行父类(ThreadPoolExecutor) 方法remove移除队列中的指定任务,成功移除后调用ScheduledFutureTask.cancel取消任务
核心方法:scheduleAtFixedRate 和 scheduleWithFixedDelay
/**
* 创建一个周期执行的任务,第一次执行延期时间为initialDelay
* 之后每隔period执行一次,不等待第一次执行完成就开始即时
*/
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (period <= 0)
throw new IllegalArgumentException();
// 构建RunnableScheduledFuture任务类型
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit), // 计算任务的延迟时间
unit.toNanos(period)); // 计算任务的执行周期
RunnableScheduledFuture<Void> t = decorateTask(command, sft); // 执行用户自定义逻辑
sft.outerTask = t; // 赋值给outerTask,准备重新入队等待下一次执行
delayedExecute(t); // 执行任务
return t;
}
/**
* 创建一个周期执行的任务,第一次执行延期任务为initialDelay
* 在第一次执行完之后延迟delay后开始下一次执行
*/
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
if (delay <= 0)
throw new IllegalArgumentException();
// 构建RunnableScheduledFuture任务类型
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),// 计算任务的延迟时间
unit.toNanos(-delay));// 计算任务的执行周期
RunnableScheduledFuture<Void> t = decorateTask(command, sft);// 执行用户自定义逻辑
sft.outerTask = t;// 赋值给outerTask,准备重新入队等待下一次执行
delayedExecute(t);// 执行任务
return t;
}
scheduleAtFixedRate和scheduleWithFixedDelay方法的逻辑与schedule类似。
注意scheduleAtFixedRate和scheduleWithFixedDelay的区别: 乍一看两个方法一模一样,其 实,在unit.toNanos这一行代码中还是有区别的。没错,scheduleAtFixedRate传的是正值,而 scheduleWithFixedDelay传的则是负值,这个值就是 ScheduledFutureTask 的period属性
核心方法:shutdown()
public void shutdown() {
super.shutdown();
}
@Override void onShutdown() {
BlockingQueue<Runnable> q = super.getQueue();
// 获取run‐after‐shutdown参数
boolean keepDelayed =
getExecuteExistingDelayedTasksAfterShutdownPolicy();
boolean keepPeriodic =
getContinueExistingPeriodicTasksAfterShutdownPolicy();
if (!keepDelayed && !keepPeriodic) { // 池关闭后不保留任务
// 依次取消任务
for (Object e : q.toArray())
if (e instanceof RunnableScheduledFuture<?>)
((RunnableScheduledFuture<?>) e).cancel(false);
q.clear(); // 消除等待队列
}
else { // 池关闭后保留任务
// Traverse snapshot to avoid iterator exceptions
// 遍历快照以避免迭代器异常
for (Object e : q.toArray()) {
if (e instanceof RunnableScheduledFuture) {
RunnableScheduledFuture<?> t =
(RunnableScheduledFuture<?>)e;
if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
t.isCancelled()) { // also remove if already cancelled
// 如果任务已经取消,移除队列中的任务
if (q.remove(t))
t.cancel(false);
}
}
}
}
tryTerminate(); // 终止线程池
}
说明: 池关闭方法调用了父类ThreadPoolExecutor的shutdown,主要介绍以下在shutdown方法中 调用的关闭钩子onShutdown方法,它的主要作用是在关闭线程池后取消并清除由于关闭策略不应该 运行的所有任务,这里主要是根据 run-after-shutdown 参数 (continueExistingPeriodicTasksAfterShutdown和executeExistingDelayedTasksAfterShutdown) 来决定线程池关闭后是否关闭已经存在的任务
再深入理解
1)ScheduledThreadPoolExecutor。下面是Executors提供的,创建固定个数的线程 ScheduledThreadPoolExecutor的API。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
ScheduledThreadPoolExecutor适用于需要多个后台线程执行的周期任务,同时为了满足资源管理的需求而需要限制后台线程数量的场景。
2)SingleThreadSchduledExecutor。下面是Executors提供的,创建单个线程的 SingleThreadSchduledExecutor的API。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory
threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
SingleThreadSchduledExecutor适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的场景。
Future接口
Future接口和实现Future接口的FutureTask类用来表示异步计算的结果,当我们把Runnable接口或 者Callable接口的实现类提交(submit)给ThreadPoolExecutor或者 ScheduledThreadPoolExecutor时,ThreadPoolExecutor或者ScheduledThreadPoolExecutor会 向我们返回一个FutureTask对象。下面是对应的API
public Future<?> submit(Runnable task) {
return e.submit(task);
}
public <T> Future<T> submit(Callable<T> task) {
return e.submit(task);
}
public <T> Future<T> submit(Runnable task, T result) {
return e.submit(task, result);
}
Runnable和Callable接口
Runnable和Callable接口的实现类都可以被ThreadPoolExecutor或者 SchduledThreadPoolExecutor执行。它们之间的区别是Runnable不会返回结果,而Callable可以返回结果。
除了可以自已创建实现Callable接口的对象外,还可以使用工厂类Executors来把一个Runnable包装成一个Callable。
下面是Executors提供的,把一个Runnable包装成Callable的API
public static Callable<Object> callable(Runnable task) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<Object>(task, null);
}
下面是Executors提供的,把一个Runnable和一个待返回的结果包装成一个Callable的API
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
当我们把一个Callable对象提交给ThreadPoolExecutor或者SchduledThreadPoolExecutor执行 时,summit()会向我们返回一个FutureTask对象。我们可以执行FutureTask.get()来等待任务执行完成。当任务完成后FutureTask.get()将会返回任务的结果。
Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码。
Spring对每个bean提供了一个scope属性来表示该bean的作用域。它是bean的生命周期。例如, 一个scope为singleton的bean,在第一次被注入时,会创建为一个单例对象,该对象会一直被复用到应用结束。
我们交由Spring管理的大多数对象其实都是一些无状态的对象,这种不会因为多线程而导致状态被破 坏的对象很适合Spring的默认scope,每个单例的无状态对象都是线程安全的(也可以说只要是无状态的对象,不管单例多例都是线程安全的,不过单例毕竟节省了不断创建对象与GC的开销)。
无状态的对象即是自身没有状态的对象,自然也就不会因为多个线程的交替调度而破坏自身状态导致线程安全问题。无状态对象包括我们经常使用的DO、DTO、VO这些只作为数据的实体模型的贫血对象,还有Service、DAO和Controller,这些对象并没有自己的状态,它们只是用来执行某些操作 的。例如,每个DAO提供的函数都只是对数据库的CRUD,而且每个数据库Connection都作为函数 的局部变量(局部变量是在用户栈中的,而且用户栈本身就是线程私有的内存区域,所以不存在线程 安全问题),用完即关(或交还给连接池)。
有人可能会认为,我使用request作用域不就可以避免每个请求之间的安全问题了吗?这是完全错误 的,因为Controller默认是单例的,一个HTTP请求是会被多个线程执行的,这就又回到了线程的安全问题。当然,你也可以把Controller的scope改成prototype,实际上Struts2就是这么做的,但有一 点要注意,Spring MVC对请求的拦截粒度是基于每个方法的,而Struts2是基于每个类的,所以把 Controller设为多例将会频繁的创建与回收对象,严重影响到了性能。
通过上面讲述其实已经说的很清楚了,Spring根本就没有对bean的多线程安全问题做出任何保证与措施。对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized、lock、CAS等 这些实现线程同步的方法了。