委托是创建线程安全类的一个最有效的策略:只需让现有的线程安全类管理所有的状态即可。
本章主要介绍一些比较有用的并发构建模块,特别是在 Java 5.0 和 Java 6.0 中引入的一些新模块,以及在使用这些模块来构造应用程序时的一些常用模式。
最早出现的同步容器类是Vector
和Hashtable
,在 JDK 1.2 及之后,又提供了一些功能类似的封装器类,这些同步容器类是由 Collections.synchronizedXxx
等工厂方法创建的。
其实现线程安全的方式是:将它们的状态封装起来,并对每个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。
同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护符合操作。
容器上常见的复合操作包括:迭代、跳转以及条件运算。在同步容器类中,这些复合操作在没有客户端加锁的情况下是线程安全的,但是在其他线程并发的修改容器时,它们可能会表现出意料之外的行为。
Vector 上可能导致混乱结果的复合操作
public static Object getLast(Vector list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
public static void deleteLast(Vector list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
假设线程A在包含10个元素的Vector上调用 getLast
,同时线程B在同一个Vector上调用 deleteLast
,这些操作交替执行如下图:
那么当执行到get操作时,会报出异常。
为了解决这个问题,我们可以采用客户端加锁的方法来保证复合操作的线程安全性:
public static Object getLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast(Vector list){
synchronized(list){
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
for (int i = 0; i < vector.size(); i ++)
dosomething(vector.get(i));
为了解决这个问题,我们可以对其进行客户端加锁操作:
synchronized(vector){
for (int i = 0; i < vector.size(); i ++)
dosomething(vector.get(i));
在设计同步容器类的迭代器时并没有考虑到并发修改的问题,并且它们表现出的行为是“及时失败(fail-fast)”的。
这意味着,当它们发现容器在迭代过程中被修改时,就会抛出一个ConcurrentModificationException异常。
虽然加锁可以避免迭代器抛出 ConcurrentModificationException,但必须要记住在所有共享容器进行迭代的地方都要加锁。实际情况可能更加复杂,因为在某些情况下,迭代器会隐藏起来。
隐藏在字符串连接中的迭代操作
public class HiddenIterator{
// 不要这么做
private final Set<Integer> set = new Hashset<Integer>();
public synchronized void add(Integer i){
set.add(i); }
public synchronized void remove(Integer i){
set.remove(i); }
public void addTenThings(){
Random r = new Random();
for (itn i = 0; i < 10; i ++)
add(r.nextInt());
// !!注意下面一行
System.out.println("DEBUG: added ten elements to " + set); //进行了隐式地迭代
}
}
set 的 toString 方法也进行了迭代操作!
除此之外,常见的隐式迭代器还有hashCode和equals,containsAll,removeAll 等方法。
在 Java 5.0 中增加了 ConcurrentHashMap,用来代替 Map 及 CopyOnWriteArrayList。
通过并发容器来代替同步容器,可以极大地提高伸缩性并降低风险
Java 5.0 中新增了两种容器:Queue、BlockingQueue。Queue 提供了几种实现,包括:ConcurrentLinkedQueue——一个传统的先进先出队列,PriorityQueue——一个(非并发的)优先队列。
BlockingQueue 扩展了 Queue,增加了可阻塞的插入和获取等操作。
阻塞队列提供了可阻塞的 put 和 take 方法,以及支持定时的 offer 和 poll 方法
生产者-消费者模式将“找出需要完成的工作”与“执行工作”这两个过程分离开,并把工作放入一个“待完成”的列表中一遍在随后处理。
在基于阻塞队列的…模式中,当数据生成时,生产者把数据放入队列,而当消费者准备处理数据,将冲队列中获取数据。
为了不至于太抽象,我们举一个寄信的例子(虽说这年头寄信已经不时兴,但这个例子还是比较贴切的)。假设你要寄一封平信,大致过程如下(引自:生产者/消费者模式的理解及实现(整理):
1、你把信写好——相当于生产者制造数据
2、你把信放入邮筒——相当于生产者把数据放入缓冲区
3、邮递员把信从邮筒取出——相当于消费者把数据取出缓冲区
4、邮递员把信拿去邮局做相应的处理——相当于消费者处理数据
常见的阻塞队列(BlockingQueue)的实现
不是很理解!
通过将多个并发的任务存入队列实现任务的串行化,并未这些串行化的任务创建唯一的一个工作进程处理。
本质:使用一个开销更小的锁(队列锁)去代替另一个可能开销更大的锁(非线程安全对象引用的锁)。
Deque是一个双端队列,实现了队列在队列头和队列尾的高效插入和移除。具体实现包括ArrayQueue和LinkedBlockingQueue。
在工作密取(Work Stealing)中,每个消费者都有各自的双端队列。如果一个消费者完成了自己双端队列中的全部工作,那么它可以从其他消费者双端队列末尾秘密地获取工作。
工作密取模式比传统的生产者-消费者模式具有更高的可伸缩性。因为在大多数情况下,它们都只是访问自己的双端队列,从而极大地减少了竞争,并且当它需要访问另一个队列时,它会从尾部获取工作。
同步工具类可以是任何一个对象,只要它根据其自身状态来协调线程的控制流。
阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量(Semaphore),栅栏(Barrier)以及闭锁(Latch)。
例如:
CountDownLatch 是一种灵活的闭锁实现,它可以使一个或多个线程等待一组事件发生。比锁状态包括一个计数器,该线程被初始化为一个正数,表示需要等待的事件数量。
countDown
方法表示对计数器递减,表示有一个事件发生了,而 await
方法会等待计数达到 0
在计时测试中使用 CountDownLatch 来启动和停止线程
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest {
public static long timeTasks(int nThreads, final Runnable task) throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
long start = System.currentTimeMillis();
for (int i = 0; i < nThreads; i++) {
int finalI = i;
Thread t = new Thread() {
@Override
public void run() {
System.out.println(String.format("线程%d启动!", finalI));
try {
startGate.await();
try {
System.out.println(String.format("第%d个线程开始运行", finalI));
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
Thread.sleep(100);
}
startGate.countDown();
endGate.await();
long end = System.currentTimeMillis();
return end - start;
}
public static void main(String[] args) throws InterruptedException {
Runnable r = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
System.out.println(timeTasks(20, r));
}
}
java.util.concurrent.Callable
Callable 接口 与 Runnable 接口类似,不同的是它们的唯一的 run 方法:
1、Callable 有返回值,Runnable 没有。
Callable 的 run() 方法使用了 泛型类,可以返回任意类型的值。
2、Callable 抛出异常 ,Runnable 没有抛出。
同时 java.util.concurrent.Executors 提供了许多方法,可以操控 Callable 在线程池中运行。
FutureTask也可以用作闭锁,FutureTask 表示的计算是通过 Callable 来实现的,相当于可生成结果的 Runnable,并且处于以下三种状态:等待运行(Waiting to run)、正在运行(Running) 和运行完成(Completed)。
FutureTask 的闭锁体现在它的get方法,如果任务完成,那么get会立即返回结果,否则get会阻塞直到任务完成进入完成状态。
FutureTask 将计算结果从执行计算的线程传递到获取这个结果的线程,且能保证传递过程能实现安全发布。
使用 FutureTask 来提前加载稍后需要的数据
public class Preloader{
//通过call方法返回执行的结果
private final FutrueTask<ProductInfo> futrue =
new FutrueTask<ProductInfo>(
new Callable<ProductInfo>(){
public ProductInfo call() throws DataLoadException{
return loadProductInfo();
}
});
private final Thread thread = new Thread(futrue);
public void start(){
thread.start(); }
public ProductInfo get()
throws DataLoadException,InterruptedException{
try{
return futrue.get();
} catch (ExecutionException ex){
Throwable cause = e.getCause();
if (cause instanceof DataLoadException) {
throw (DataLoadException) cause;
} else {
throw launderThrowable(cause);
}
}
}
}
注意:在Preloader中,当get方法抛出ExecutionException时,可能是这三种情况:Callable抛出的异常,RuntimeExcetpion,以及Error。Preloader会首先检查已知的受检查类型异常,并重新抛出给它们,剩下的就是未检查异常交给了launder Throwable来处理。
public static RuntimeException launderThrowable(Throwable t){
if (t instanceof RuntimeException)
return (RuntimeException) t;
else if (t instanceof Error)
return (Error) t;
else
throw new IllegalStateException("Not unchecked",t);
}
使用 Semaphore 为容器设置边界
public class BoundedHashSet<T>{
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet(int bound){
this.set = Collections.synchronizedSet(new Hashset<T>());
sem = new Semaphore(bound); // 初始化 bound 为许可数量
}
public boolean add(T o) throws InterruptedException{
sem.acquire(); // 获得一个许可
boolean wasAdded = false;
try{
wasAdded = set.add(o);
return wasAdded;
} finally{
if (!wasAdded)
sem.release(); // 如果添加失败则释放一个许可
}
}
public boolean remove(Object o){
boolean wasRemoved = set.remove(o);
if (wasRemoved)
sem.release(); // 删除操作,释放一个许可
return wasRemoved;
}
}
闭锁是一次性对象,一旦进入终止状态,就不能被重置。
栅栏(Barrier)类似于闭锁,它能阻塞一组线程直到某个事件发生。
栅栏与闭锁的关键区别在于:所有线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
当线程达到栅栏时将调用await方法,这个方法将阻塞一直到所有的线程都达到这个栅栏位置。
public class BarrierTest implements Runnable {
private final CyclicBarrier cyclicBarrier;
private final String name;
private final Random r = new Random();
public BarrierTest(CyclicBarrier cyclicBarrier, String name){
this.cyclicBarrier = cyclicBarrier;
this.name = name;
}
@Override
public void run() {
System.out.println(name + "正在打桩....");
try {
Thread.sleep(r.nextInt(5000));
System.out.println(name + "完成打桩。");
// 阻塞线程,直到所有线程都到达栅栏
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + ": 其他人都打完桩了,开始搭桥了。");
}
public static void main(String[] args){
ExecutorService executorService = Executors.newFixedThreadPool(3);
//定义栅栏
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
BarrierTest work1 = new BarrierTest(cyclicBarrier, "张三");
BarrierTest work2 = new BarrierTest(cyclicBarrier, "李四");
BarrierTest work3 = new BarrierTest(cyclicBarrier, "王五");
executorService.execute(work1);
executorService.execute(work2);
executorService.execute(work3);
executorService.shutdown();
}
}
Exchanger是一种双方栅栏,各方在栅栏位置上交换数据。
Exchanger可以在两个线程之间交换数据,只能是2个线程,不支持更多的线程之间交换数据。
当线程A调用Exchange对象的exchange方法之后,它会陷入阻塞状态,直到线程B也调用exchange方法,然后以线程安全的方式交换数据,之后线程A和B继续运行。
举个例子:我们模拟将钱和商品进行交换,线程A持有钱,线程B持有商品,两者之间进行交换。
import java.util.Random;
import java.util.concurrent.Exchanger;
public class ExchangeTest {
private static final Random r = new Random();
public static void main(String[] args) {
//定义Exchanger
Exchanger<String> exchanger = new Exchanger<>();
new Money(exchanger).start();
new Product(exchanger).start();
}
static class Money extends Thread {
private String data;
private Exchanger<String> exchanger = null;
Money(Exchanger<String> exchanger) {
this.exchanger = exchanger;
data = "钱";
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据<" + data + ">交换出去");
try {
Thread.sleep(r.nextInt(3000));
//进行交换数据
data = exchanger.exchange(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "换回来的数据为<" + data + ">");
}
}
static class Product extends Thread {
private String data;
private Exchanger<String> exchanger = null;
Product(Exchanger<String> exchanger) {
this.exchanger = exchanger;
data = "商品";
}
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据<" + data + ">交换出去");
try {
Thread.sleep(r.nextInt(3000));
//进行交换数据
data = exchanger.exchange(data);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() + "换回来的数据为<" + data + ">");
}
}
}
大多数并发应用程序都是围绕"任务执行(Task Execution)"来构造的。理想情况下,各个任务之间是相互独立的:任务并不依赖于其他任务的状态、结果或边界效应。
当负荷过载时,应用程序的性能应该是逐渐降低,而不是直接失败。所以应该选择清晰的任务边界以及明确的任务执行策略。
每当看到下面这种形式的代码时: new Thread(runnable).start()
。并且你希望获得一种更灵活的执行策略时,请考虑使用Executor来代替Thread.
Executor:执行的任务有4个生命周期阶段:创建、提交、开始和完成。
由于有些任务可能要执行很长的时间,因此通常希望能够取消这些任务。在Executor框架中,已提交但尚未开始的任务可以取消,但对于那些已经开始执行的任务,只有当它们能响应中断时,才能取消。取消一个已经完成的任务不会有任何影响。
标准的 Executor 实现,即一个固定长度的线程池,可用容纳 100 个线程。
public class TaskExecutionWebServer {
private static final int NTHREADS = 100;
private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
handleRequest(connection);
}
};
exec.execute(task);
}
}
private static void handleRequest(Socket connection) {
// request-handling logic here
}
}
我们可以很容易地将 TaskExecutionWebServer 修改为类似 ThreadPerTaskWebServer 的行为 只需使用一个为每个请求都创建新线程的Executor
为每个请求启动一个新线程的Executor
public class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
};
}
线程池从字面上理解是之管理一组同构工作线程的资源池。线程池是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程(Work Thread)的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。
在线程池中执行任务比为每个任务分配一个线程优势更大。通过重用现有的线程而不是新建线程,可用处理在多个请求时分摊在线程创建和销毁过程中产生的巨大开销。
通过适当调整线程池大小,可以创建足够的的线程以便使处理器保持忙碌状态,同时还可以防止过多现场相互竞争资源而耗尽内存或失败。
可以使用 Executors 中的静态工厂方法来创建一个线程池:
newFixedThreadPool
:创建一个固定长度的线程池,提交一个任务就创建一个线程,直到达到线程池最大容量,此时线程池规模不再变化(如果某个线程出现未预期的 Exception 而结束,那么线程池会补充一个新的线程)newCacheThreadPool
:创建一个可缓存的线程池,如果当前规模超过处理需求,那么将回收空闲线程,对线程池规模不存在任何限制newSingleThreadExecutor
:一个单线程的 Executor,它创建单个工作者线程来执行任务。能确保线程池中任务在按队列的顺序串行执行newScheduledThreadPool
:创建一个固定长度的线程池,以延迟或定时的方式来执行任务,类似于 TimerExecutor 的实现通常会创建线程来执行任务,但 JVM 只要在除守护线程外的其他线程全部终止后才会退出。因此,如果无法正确地关闭 Executor,那么 JVM 将无法结束。
当关闭应用程序时,可能采用最平缓的方式关闭——等待所有任务完成且期间不接受任何新的任务,也可能采用粗暴的方式关闭——直接拉电闸:)。
为了解决 Executor 的生命周期问题,Executor 扩展了 ExecutorService 接口,添加了一些用于生命周期管理的方法(还有一些用于任务提交的方法)。
ExecutorService 中的声明周期管理方法
public interface ExecutorService extends Executor {
void shutdown(); // 执行平缓的关闭过程
List<Runnable> shutdownNow(); // 粗暴关闭
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
//其它用于任务提交的便利方法
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
//其它用于任务提交的便利方法
......
}
shutdown 执行平缓的关闭过程:不再接受新任务,等待已经提交的任务完成——包括那些还未开始的任务。
shutdownNow 执行粗暴的关闭过程:尝试取消所有运行中的任务,并且不在启动队列中未开始执行的任务。
在 ExecutorService 关闭后提交的任务将由 “拒绝执行处理器(Rejected Execution Handler)”来处理,会抛弃任务或使得 execute 方法抛出一个未检查的 RejectedExecutionException 。
等待所有任务完成后 ExecutorService 将进入终止状态。可以调用 awaitTermination
来等待 ExecutorService 到达终止状态。
支持关闭操作的 Web 服务
public class LifecycleWebServer {
private final ExecutorService exec = Executors.newCachedThreadPool();
public void start() throws IOException {
ServerSocket socket = new ServerSocket(80);
//线程池的关闭即意味着Web服务器关闭,Web服务器逻辑不再执行
while(!exec.isShutdown()) {
try {
final Socket conn = socket.accept();
exec.execute(new Runnable() {
@Override
public void run() {
handleRequest(conn);
}
});
} catch(RejectedExecutionException e) {
if(!exec.isShutdown()) {
log("task submition rejected ",e);
}
}
}
}
/**关闭服务器*/
public void stop() {
exec.shutdown();//关闭线程池}
/**处理请求*/
void handleRequest(Socket connection) {
Request req = readRequest(connection);
if(isShutdownRequest(connection))
stop();
else
dispatchRequest(req);
}
}
ScheduledThreadPoolExecutor 能正确处理Timer表现出错误行为的任务。
Timer 在执行所有定时任务时是单线程的,且 TImerTask 抛出一个未受查异常,那么 Timer 将表现出一个糟糕的行为(线程泄漏)。
如果要构建自己的调度服务,可以使用DelayQueue,他实现了BlockingQueue,并为ScheduledThreadPoolExecutor 提供调度功能。DelayQueue管理着一组Delayed对象。每个Delayed对象都有一个相应的延迟时间:在 DelayedQueue 中,只有某个元素逾期后,才能从 DelayQueue 中执行 take 操作。从DelayQueue 中返回的对象将根据他们的延迟时间进行排序。
错误的 Timer 行为
public class OutOfTime {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
timer.schedule(new ThrowTask(), 1);
SECONDS.sleep(1);
timer.schedule(new ThrowTask(), 1);
SECONDS.sleep(5);
}
static class ThrowTask extends TimerTask {
public void run() {
throw new RuntimeException();
}
}
}
Executor框架帮助指定执行策略,但如果使用Executor,必须将任务表述成一个Runnable。大多数服务器应用程序都存在一个明显的 任务边界:单个客户请求。
但有时候任务边界是模糊的,即使是服务器,单个客户请求仍然有可待发掘的并行性,例如DB服务器。
Executor 可以输入 Runnable 和 Callable。对于 Callable 对象有返回值,要使用 submit 方法,其返回值是一个 Future 对象。而 Runnable 则可以使用 execute 和 submit。
// AbstractExecutorService 中的三种 submit 实现
public <T> Future<T> submit(Callable<T> task) {
// Callable
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
public Future<?> submit(Runnable task) {
// 不带参数的 Runnable
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
// 带参数的 Runnable
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
// AbstractExecutorService 中的 newTaskFor 实现,返回一个 RunnableFuture 对象
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
// RunnableFuture 接口实现
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
// 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;
}
FutureTask 源码
/**
* FutureTask是Future以及Runnable的共同实现,
* 并在Runnable的run中封装了Callable逻辑的调用,以及对其返回结果进行描述的其它操作
* 这使得线程的调用可以有返回结果
*/
public class FutureTask<V> implements RunnableFuture<V> {
private Callable<V> callable; //正在的业务逻辑
private Object outcome; // 业务逻辑返回的结果封装对象
private volatile Thread runner;
//构造函数1 Runnable实现类中引进 业务逻辑
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
//构造函数2 :封装了业务逻辑的Runnable实现类,转化为可返回结果的Callable!!
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
/*设置返回结果*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
/*
* Runnable实现类的真正业务逻辑运行处,此处封装返回结果,释放资源
* 真正的逻辑 来着从构造函数引进来的Callable接口实现的调用
*/
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //业务逻辑真正被调用的地方
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result); //设置返回结果
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
/*获取业务逻辑返回值,返回类型是Callable的泛型*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
/*真正的返回值是类成员变量:outcome*/
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
}
AbstractExecutorService
是ThreadPoolExecutor
的基类,其中定义了newTaskFor
接口,默认逻辑就是生成FutureTask
对象的工厂;和定义submit
接口,用来提交Callable
的任务,内部用FutureTask
来进行中转。
Executor 框架的成员及关系
Executor 框架的使用
管理一系列的Future对象,可以使用ExecutorService.invokeAll
接口,一次性等待所有任务都完成;也可以使用内部包裹了BlockingQueue
的ExcectorCompletionService
,其管理的FutureTask在完成后会将自身加到阻塞队列中,而外部调用程序通过take队列数据可实现每得到一个数据就处理的逻辑。