1.介绍
1.1 Executor框架的两级调度模型
应用程序通过Executor框架控制上层的调度;而下层的调度由操作系统内核控制,下层的调度不受应用程序的控制。
1.2 Executor框架的结构与成员
Executor框架主要由以下3大部分组成:
1.任务。包括被执行任务需要实现的接口:Runnable接口或Callable接口。
2.任务的执行。包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)。
3.异步计算的结果。包括接口Future和实现Future接口的FutureTask类。
2.ThreadPoolExecutor
Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。
总结
线程池类型 | 阻塞队列类型 | 饱和策略(默认) | 特点 | 适用场景 |
---|---|---|---|---|
FixedThreadPool | LinkedBlockingQueue(无界) | AbortPolicy | 固定线程数,任务排队 | 任务数量相对稳定,需要按顺序处理 |
SingleThreadExecutor | LinkedBlockingQueue(无界) | AbortPolicy | 单线程,任务顺序执行 | 对任务执行顺序要求严格,任务量不大 |
CachedThreadPool | SynchronousQueue(无存储) | AbortPolicy | 动态线程数,无任务排队 | 大量短期任务,任务频率不固定 |
线程池状态
构造方法
提交任务
关闭线程池
shutdown
shutdownNow
awaitTermination
2.1 FixedThreadPool
2.1.1 介绍
FixThreadPool被称为可重用固定线程数的线程池。
keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将被终止。
FixThreadPool把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。
2.1.3 无界队列LinkedBlockingQueue作为线程池的工作队列
2.1.4 适用场景
FixedThreadPool适用于为了满足资源管理的需求,而 需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。适用于任务量已知,相对耗时的任务。
2.2 SingleThreadPool
2.2.1 介绍
希望多个任务排队执行,任务数为1,当任务数多于1时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会释放。
跟自己创建单线程的区别:
2.2.2 使用
使用单个Worker线程。
SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。SingleThreadExecutor使用无界队列作为工作队列对线程池带来的影响与FixedThreadPool相同。
2.2.3 适用场景
SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。
2.3 CachedThreadPool
2.3.1 介绍
CachedThreadPool是一个会根据需要创建新线程的线程池。
直白地讲,它没有容量,没有线程来取是放不进去的。它还可以无限创建。
2.3.2 构造函数
corePoolSize设置为0,maximumPoolSize设置为Integer.MAX_VALUE,keepAliveTime设置为60L(意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止)。
2.3.3 使用
CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,
但CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于 maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下, CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。
SynchronousQueue是一个没有容量的阻塞队列。每个插入操作必须等待另一 个线程的对应移除操作,反之亦然。CachedThreadPool使用SynchronousQueue,把主线程提交的任务传递给空闲线程执行。
CachedThreadPool中任务传递的示意图如图所示。
2.3.4 适用场景
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。
3.ScheduledThreadPoolExecutor
3.1 Timer的缺点
Timer对应的是单个后台进程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。
3.2 运行
任务队列DelayQueue是一个无界队列。
执行主要分两大部分:
1.当调用scheduleAtFixedRate(以上一个任务开始的时间计时,到时间如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行)或scheduleWithFixedDelay(从上次任务结束时开始算)方法时,会向DelayQueue添加一个ScheduledFutureTask。
2.线程池的线程从DelayQueue中获取ScheduleFutureTask,然后执行任务。
3.3 异常
默认情况下,即使第一个任务出现异常,第二个任务也会照常执行。
异常的处理方法:
1.用try catch捕获
2.Future接收结果,get()获取异常信息
3.4 ScheduledThreadPoolExecutor实现
3.4.1 ScheduledFutureTask
3.4.2 DelayQueue
DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的Scheduled- FutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个 ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就 是说,如果两个任务的执行时间相同,那么先提交的任务将被先执行)。
3.4.3 执行任务的四个步骤
1.线程从DelayQueue中获取已到期的ScheduledFutureTask
2.线程执行这个ScheduledFutureTask
3.线程修改这个ScheduledFutureTask的time变量为下次要执行的时间
4.线程将这个ScheduledFutureTask放回到DelayQueue
3.4.4 获取任务的过程
获取任务分3大步骤:
1.获取Lock
2.获取周期任务
(1)如果PriorityQueue为空,当前线程到Condition中等待;否则执行下面的2.2
(2)如果PriorityQueue的头元素的time时间比当前时间大,到Condition中等待到Time时间,否则执行下面的2.3
(3)获取PriorityQueue的头元素;如果PriorityQueue不为空,则唤醒在Condition中等待的所有线程。
3.释放Lock
ScheduledThreadPoolExecutor在一个循环中执行步骤2,直到线程从PriorityQueue获取到一个元素之后,才会退出循环。
3.4.5 添加任务
向DelayQueue添加任务的3大步骤:
1.获取Lock
2.添加任务
(1)向PriorityQueue添加任务
(2)如果添加的任务是PriorityQueue的头元素,则唤醒在Condition中等待所有线程
3.释放Lock
4.FutureTask
代表异步计算的结果。
4.1 FutureTask实现的接口
FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口。
所以FutureTask既能当做一个Runnable直接被Thread执行,也能作为Future用来得到Callable的计算结果。
Callable接口
public interface Callable<V> {
V call() throws Exception;
}
Future接口
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
4.2 FutureTask简介
FutureTask除了实现Future接口外,还实现了Runnable接口。因此,FutureTask可以交给Executor执行,也可以由调用线程直接执行(FutureTask.run())。
FutureTask可以处于下面3种状态:
1.未启动。run之前
2.已启动。run被执行过程中
3.已完成。执行完后正常结束,或被取消(FutureTask.cancel(…)),或执行FutureTask.run()方法时抛出异常而异常结束,FutureTask处于已完成状态。
当FutureTask处于未启动或已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即返回结果或抛出异常。
4.3 内部实现
构造函数
传入Callable
保存在this.callable字段中
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
传入Runnable
会把Runnable封装在callable中
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
7种不同状态
//内部持有的callable任务,运行完毕后置空
private Callable<V> callable;
//从get()中返回的结果或抛出的异常
private Object outcome; // non-volatile, protected by state reads/writes
//运行callable的线程
private volatile Thread runner;
//使用Treiber栈保存等待线程
private volatile WaitNode waiters;
//任务状态
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
state是volatile类型的,也就是说只要有任何一个线程修改了这个变量,那么其他所有的线程都会知道最新的值。
7种状态具体表示:
NEW:表示是个新的任务或者还没被执行完的任务。这是初始状态。
COMPLETING:任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态。
NORMAL:任务已经执行完成并且任务执行结果已经保存到outcome字段,状态会从COMPLETING转换到NORMAL。这是一个最终态。
EXCEPTIONAL:任务执行发生异常并且异常原因已经保存到outcome字段中后,状态会从COMPLETING转换到EXCEPTIONAL。这是一个最终态。
CANCELLED:任务还没开始执行或者已经开始执行但是还没有执行完成的时候,用户调用了cancel(false)方法取消任务且不中断任务执行线程,这个时候状态会从NEW转化为CANCELLED状态。这是一个最终态。
INTERRUPTING: 任务还没开始执行或者已经执行但是还没有执行完成的时候,用户调用了cancel(true)方法取消任务并且要中断任务执行线程但是还没有中断任务执行线程之前,状态会从NEW转化为INTERRUPTING。这是一个中间状态。
INTERRUPTED:调用interrupt()中断任务执行线程之后状态会从INTERRUPTING转换到INTERRUPTED。这是一个最终态。
有一点需要注意的是,所有值大于COMPLETING的状态都表示任务已经执行完成(任务正常执行完成,任务执行异常或者任务被取消)。
4.3 FutureTask的使用
可以把FutureTask交给Executor执行;也可以通过ExecutorService.submit(…)方法返回一个 FutureTask,然后执行FutureTask.get()方法或FutureTask.cancel(…)方法。除此以外,还可以单独 使用FutureTask。
第一种方式: Future + ExecutorService
第二种方式: FutureTask + ExecutorService
第三种方式: FutureTask + Thread
第一种方式: Future + ExecutorService
import java.util.concurrent.*;
public class FutureWithExecutorService {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> callableTask = () -> {
// 这里模拟一个耗时任务,比如计算1到100的和
return IntStream.rangeClosed(1, 100).sum();
};
Future<Integer> future = executor.submit(callableTask);
// 主线程可以继续做其他事情,这里简单等待一下
Thread.sleep(1000);
Integer result = future.get();
System.out.println("任务结果: " + result);
executor.shutdown();
}
}
第二种方式: FutureTask + ExecutorService
import java.util.concurrent.*;
public class FutureTaskWithExecutorService {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Integer> callableTask = () -> {
// 模拟耗时任务,如计算200到300的和
return IntStream.rangeClosed(200, 300).sum();
};
FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
executor.submit(futureTask);
// 主线程可进行其他操作,这里等待片刻
Thread.sleep(1000);
Integer result = futureTask.get();
System.out.println("任务结果: " + result);
executor.shutdown();
}
}
第三种方式: FutureTask + Thread
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class FutureTaskWithThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callableTask = () -> {
// 模拟耗时任务,如计算50到150的和
return IntStream.rangeClosed(50, 150).sum();
};
FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
Thread thread = new Thread(futureTask);
thread.start();
// 主线程可做其他事,这里稍作等待
Thread.sleep(1000);
Integer result = futureTask.get();
System.out.println("任务结果: " + result);
}
}
4.4 FutureTask的实现
FutureTask的实现基于AQS。
每一个基于AQS实现的同步器都会包含两种类型的操作:
1.至少一个acquire操作。这个操作阻塞调用线程,除非/直到AQS的状态允许这个线程继续执行。FutureTask的acquire操作为get()/get(long timeout,TimeUnit unit)方法调用。
2.至少一个release操作。这个操作改变AQS的状态,改变后的状态可允许一个或多个阻塞线程被解除阻塞。FutureTask的release操作包括run()方法和cancel(…)方法。
FutureTask的设计示意图:
get方法
run方法
级联唤醒