在上一篇博文中 , 我们了解了线程池相关概念, ExecutorService 与常用的创建线程池的方法一些参数概念, 大概了解了线程池的工作流程. 介绍了三种任务队列, 四种拒绝方式, 五种线程池模型. 本文将继续介绍 ExecutorService中相关的类和接口的概念 , 具体来说 , 是Callable 和 Future相关的使用.
1 Callable
1.1 Runnable
关于 Runnable 接口应该是比较熟悉了吧 , 实现其接口即可创建一个线程, run方法中重写线程要执行的任务…
public interface Runnable {
public abstract void run();
}
多线程开发中经常使用到Runnable接口, 它定义run方法, 只要对象实现这个方法 , 将对象作为参数输入到new Thread(Runnable A ),线程一旦start(),那么就自动执行了, 没有任何的返回结果,无法知道什么时候结束,适用于完全异步的任务,不用关心结果.
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " hello world ");
}
} , "thread - 1").start();
1.2 Callable
可以猜到的是, callable是带返回类型的
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
Callable定义的接口call(), 它能够抛出异常 . 并且能够有一个返回结果。实现了Callable要想提交到线程池中, 直接通过 ExecutorService.submit(new CallAbleTask(i)),但是返回的结果是Future,结果信息从Future里面取出,具体的业务逻辑在call中执行. 事实上, callable 也确实结合线程池使用的.
2 Future 及其子接口/ 实现类
2.1 Future
进入Future 源码 可以看到其提供的五个方法
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);//用来取消任务, 如果取消任务成功则返回true ,如果取消任务失败则返回false
boolean isCancelled();//表示任务是否被取消成功, 如果任务正常完成前被取消成功, 返回true
boolean isDone();// 表示任务是否已经完成, 若任务完成, 返回 true
V get() throws InterruptedException, ExecutionException;// 用来获取执行结果, 这个方法会阻塞一哈直到等到任务执行完成才返回
V get(long timeout, TimeUnit unit)//用来获取执行结果, 如果在指定时间内, 还没获取到结果, 就直接返回null
throws InterruptedException, ExecutionException, TimeoutException;
}
总的来说, Future能够控制Callable对象的执行,检测是否做完, 可以取消它任务, 可以阻塞式获取结果, 也可以等待一段时间内获取结果.
即以下三种功能:
- 判断任务是否完成
- 能够中断任务
- 能够获取任务执行的结果
2.2 RunnableFuture 接口和它的实现类 FutureTask
RunnableFuture继承了 Runnable 和 Future, 即相当于在Future接口的基础上增加了 run() 方法. FutureTask是这个接口的实现类, 由此可通过该FutureTask 来创建线程, 结合Callable使用, 就能够用FutureTask控制线程执行的任务返回值哦!
看 FutureTask 的构造函数
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable, NEW = 0
}
看, 传入的参数果然是 Callable , 可以推之, 其继承实现自Future的 get 方法, 是可以把 类型V 返回的 . 除了传入Callable进行构造, 传 Runnable 也是可以的, 不过还得带一个线程执行结果
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
现在我们知道了FutureTask 是Runnable 和 Future 的结合, 如果我们把Runnable 比作是生产者, Future 比作是消费者, 那么FutureTask 是被这两者共享的 , 生产者运行run 方法计算结果, 消费者通过get方法获得结果.
作为生产者消费者模式 , 其中一个很重要的机制, 就是若生产者数据还没准备的时候, 消费者会被阻塞. 当生产者数据准备好了以后会唤醒消费者继续执行.那么这种像阻塞队列的机制在FutureTask里是怎么实现的呢?
state
private static final int NEW = 0; // NEW 新建状态,表示这个 FutureTask还没有开始运行
// COMPLETING 完成状态, 表示 FutureTask 任务已经计算完毕了
// 但是还有一些后续操作,例如唤醒等待线程操作,还没有完成。
private static final int COMPLETING = 1;
// FutureTask 任务完结,正常完成,没有发生异常
private static final int NORMAL = 2;
// FutureTask 任务完结,因为发生异常。
private static final int EXCEPTIONAL = 3;
// FutureTask 任务完结,因为取消任务
private static final int CANCELLED = 4;
// FutureTask 任务完结,也是取消任务,不过发起了中断运行任务线程的中断请求
private static final int INTERRUPTING = 5;
// FutureTask 任务完结,也是取消任务,已经完成了中断运行任务线程的中断请求
private static final int INTERRUPTED = 6;
run方法
public void run() {
// 如果状态 state 不是 NEW,或者设置 runner 值失败
// 表示有别的线程在此之前调用 run 方法,并成功设置了 runner 值
// 保证了只有一个线程可以运行 try 代码块中的代码。
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) { // 只有 c 不为 null 且状态 state 为 NEW 的情况
V result;
boolean ran;
try {
result = c.call(); //调用 callable 的 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);
}
}
run 方法就是调用callable的 call 方法返回结果值result , 根据是否发生异常吗调用set(result)或 setExecution(ex) 方法表示FutureTask 任务完结.
get 方法
get方法来自于Future, 就是阻塞获取线程执行结果, 这里主要做两件事情
- 判断当前状态, 如果状态小于等于 COMPLETING, 表示FutureTask 任务还没有完结, 所以调用 awaitDone 方法, 让线程等待.
- report 返回结果值或抛出异常
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
awaitDone方法
如果当前的结果还没有执行完, 把当前线程插入到等待队列
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false; // 节点是否已添加
for (; ; ) {
// 如果当前线程中断标志位是 true,
// 那么从列表中移除节点 q,并抛出 InterruptedException 异常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) { // 当状态大于 COMPLETING 时,表示 FutureTask 任务已结束。
if (q != null)
q.thread = null; // 将节点 q 线程设置为 null,因为线程没有阻塞等待
return s;
}// 表示还有一些后序操作没有完成,那么当前线程让出执行权
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
//表示状态是 NEW,那么就需要将当前线程阻塞等待。
// 就是将它插入等待线程链表中,
else if (q == null)
q = new WaitNode();
else if (!queued)
// 使用 CAS 函数将新节点添加到链表中,如果添加失败,那么queued 为 false,
// 下次循环时,会继续添加,知道成功。
queued = UNSAFE.compareAndSwapObject(this,
waitersOffset,
q.next = waiters, q);
else if (timed) {// timed 为 true 表示需要设置超时
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos); // 让当前线程等待 nanos 时间
} else
LockSupport.park(this);
}
}
//被阻塞的线程,会等到 run 方法执行结束之后被唤醒
report 方法
根据传入的状态值 s,来决定是抛出异常,还是返回结果值。这个两种情况都表示 FutureTask 完结了
private V report(int s) throws ExecutionException {
Object x = outcome;//表示 call 的返回值
if (s == NORMAL) // 表示正常完结状态,所以返回结果值
return (V) x;
// 大于或等于 CANCELLED,都表示手动取消 FutureTask 任务,
// 所以抛出 CancellationException 异常
if (s >= CANCELLED)
throw new CancellationException();
// 否则就是运行过程中,发生了异常,这里就抛出这个异常
throw new ExecutionException((Throwable) x);
}
本文主要介绍了与Runnable 很相似的 Callable , 以及一个Future接口及其他的子接口 RunnableFuture 和 实现类FutureTask, 主要操纵线程任务中的返回值. 在有线程池参与的多线程环境中, 运用FutureTask 操纵各线程执行任务的返回结果是很常见的. 有了这些基础, 结合上篇文章中讲解的一些概念 , 下篇文章即可较详细介绍 ExecutorService 与各种ThreadPoolExecutor 中的线程池了
[Java高并发系列(5)][详细]Java中线程池(1)–基本概念介绍
往期回顾
[Java高并发系列(1)]Java 中 synchronized 关键字详解 + 死锁实例
[Java高并发系列(2)]Java 中 volatile 关键字详解 + volatile 与 sychronized 区别
[Java高并发系列(3)]Java 中 CountDownLatch介绍 + 一道面试题
[Java高并发系列(4)]Java 中 ReentrantLock 介绍 + 一道面试题