1.概述
Callable是java多线程中一个函数式接口,它是在java1.5的时候出现的,它能够返回线程执行结果并且能够抛出线程执行过程中的异常。Callable的出现主要是为了弥补继承Thread或实现Runnable接口的线程执行完成后,无法获得线程执行后的结果。Callable在日常的开发中,应用的场景也比较多,但是Callable的使用,也并不是百利无一害。本文将从Callable的使用、相关源码的分析以及使用中的注意事项来进行分析,帮助大家更好的理解Callable。
函数式接口:一个接口中只包含有一个方法。接口可以使用@FunctionalInterface注解修饰,也可以不使用@FunctionalInterface注解修饰。
2.实战演练
2.1 Callable与Runnable分析
首先来看一下Callable接口与Runable接口的源码:
@FunctionalInterface
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;
}
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
由上述代码可以看出,Callable接口与Runable接口都是函数式接口,区别在于以下:
1.Runnable 接口内部是一个抽象的run()方法,该方法的返回类型为void,所以执行后无法获取返回值;
2.Callable接口内部是一个call()方法,该接口是一个泛型接口(返回类型由传入类型决定)。
2.2 一个简单的Callable案例
2.2.1 测试代码
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@Slf4j
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask<String> futureTask = new FutureTask(myThread);
new Thread(futureTask, "A").start();
String result = (String) futureTask.get();
log.info("result:{}", result);
}
}
@Slf4j
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
log.info("test callable()");
TimeUnit.SECONDS.sleep(5);
return "callable";
}
}
运行结果如下:
2.3 针对上述案例的分析
1.Callable和FutureTask是什么关系,为什么要和它一起使用?
解答这个问题,首先要看一下Thread的构造函数类型,具体如下图所示:
由上图可知,Thread的构造函数中只接受Runable对象、线程组ThreadGroup和String类型的线程名称;JAVA线程正常启动运行的方式应该只有一种:通过调用Thread类的start()方法来启动线程(不考虑线程池的启动方式)。 很多博客概念不清,将继承Thread类、实现Runable接口、实现Callable接口算作启动线程的方式,我认为是不正确的,这只是JAVA实现多线程编程的方式,而不是真正意义上的启动线程。
所以如何能够让实现了Callable接口的线程以Thread.start()方法来启动成为了一个问题?
而另一个问题是如何返回线程执行后的结果?
为了解决上述这两个问题,JAVA1.5中引入了FutureTask类,具体类图如下所示:
由上述类图可知,FutureTask类实现了RunnableFuture接口,该接口同时实现了Runable和Future接口,因此FutureTask可以通过Thread类的构造方法传入,再利用Thread.start()方法进行启动。另一个FutureTask内部有接收对象为Callable的构造函数,同时有可以获取线程执行结果的get()方法(具体源码会在下节分析),来获取实现Callable接口的返回结果。
上述案例中的UML图如下:
2.由运行结果来看,线程被阻塞了一段时间?
这是因为Future接口定义了一系列的方法,用于控制任务和获取任务的结果。下面是Future接口上的注释:
上面注解的大致意思是:
{@code Future}表示异步计算的结果。提供了检查计算是否完成、等待其完成以及检索计算结果的方法。只有在计算完成后,才能使用方法{@code get}检索结果,必要时将其阻塞,直到准备就绪。取消由{@code cancel}方法执行。还提供了其他方法来确定任务是正常完成还是取消。一旦计算完成,就不能取消计算。如果希望异步计算可以被取消而且不提供可用的计算结果,同时为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。
简单翻译一下就是:Future接口获取结果是阻塞式的,并且如果不想提供可用的结果,可用返回一个null作为结果。 所以,线程阻塞是因为计算线程MyThread 休眠了5s,因而阻塞了5s。
3.代码分析
3.1 Future
Future接口表示异步计算的结果,内部定义了一系列接口,包括检查计算是否完成、等待计算完成、获取计算结果等。下面详细分析一下Future的方法:
方法名称 | 源码 | 解释 |
---|---|---|
cancel | boolean cancel(boolean mayInterruptIfRunning); | 调用该方法将试图取消对任务的执行,如果任务已经执行完成、已取消或无法取消,则会返回失败,当该方法调用时任务还没有开始,方法调用成功而且任务将不会再执行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否执行此任务的线程应该以试图停止任务被中断。此方法返回后调用isDone 方法将返回 true 。后续调用 isCancelled 总是返回第一次调用的返回值。 |
isCancelled | boolean isCancelled(); | 判断任务是否被取消,如果任务在完成前被取消,将返回 true |
isDone | boolean isDone(); | 判断任务是否完成,任务已经结束,在任务完成、任务取消、任务异常的情况下都返回 true |
get | V get() throws InterruptedException, ExecutionException; | 获取任务执行结果,调用时会阻塞线程,直至获取结果 |
get | V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; | 指定时间内获取任务执行结果,如果超过该时间未获取到,则会返回null并抛出超时异常 |
3.2 FutureTask
Future主要是定义了关于异步计算的一系列接口,而FutureTask是Future的具体实现类,下面详细分析一下FutureTask的源码。首先来看下FutureTask内部的属性:
//state变量用来保存任务的状态,它的取值从0~6,一共7个值,下面会单独针对这七个状态进行解释说明
private volatile int state;
//callable表示接收的任务对象,提交的任务会放到这个属性中
private Callable<V> callable;
//保存call方法执行后的返回值
private Object outcome; // non-volatile, protected by state reads/writes
//保存执行当前任务的线程
private volatile Thread runner;
//保存用来获取任务返回值的等待队列
private volatile WaitNode waiters;
state保存的任务状态值如下:
//初始状态,当新建一个FutureTask任务时,默认状态为NEW,也表示当前任务未执行
private static final int NEW = 0;
//任务正常执行结束中,尚未正常结束,处于一种临界状态,主要包含以下两种情况
//1.任务执行完成,还未将结果返回给属性outcome
//2.任务执行异常,捕获异常并进行处理,还未将异常结构返回给outcome
private static final int COMPLETING = 1;
//任务正常执行完成,并将任务的返回值赋给outcome属性之后,会处于NORMAL状态
private static final int NORMAL = 2;
//当前任务执行中发生了异常,异常向上抛出
private static final int EXCEPTIONAL = 3;
//当前任务被取消
private static final int CANCELLED = 4;
//调用cancel(true),线程中断之前的状态
private static final int INTERRUPTING = 5;
//调用调用cancel(true),线程中断之后的状态
private static final int INTERRUPTED = 6;
看完属性以后,接下来看下其内部的方法,具体如下图所示:
3.2.1 构造方法
//接收实现Callable1接口的任务对象,并将初始状态值设置为NEW
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
//接收实现Runnable接口的任务对象,并将初始状态值设置为NEW
public FutureTask(Runnable runnable, V result) {
//使用了装饰者模式,将传进来的Runnable对象转换成了callable对象,外部线程通过get获取值时,获取到的可能是null,也可能是传进来的结果result
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
3.2.2 report(int s)
//返回已完成任务的结果,或抛出任务执行过程中的异常
private V report(int s) throws ExecutionException {
Object x = outcome;
//判断状态是否为NORMAL,若是,返回结果outcome
if (s == NORMAL)
return (V)x;
//判断状态值是否大于 取消状态的值,若是,表明任务被取消或被其他情况终止,抛出CancellationException异常
if (s >= CANCELLED)
throw new CancellationException();
//否则,抛出线程执行异常
throw new ExecutionException((Throwable)x);
}
3.2.3 isCancelled()
//判断任务状态是否取消,若状态值大于等于取消状态值,返回true(表明已取消),否则返回false
public boolean isCancelled() {
return state >= CANCELLED;
}
3.2.4 isDone()
// 判断任务是否执行完成,若完成返回true,否则返回false
public boolean isDone() {
//默认判断状态不为NEW的均为完成状态
return state != NEW;
}
3.2.5 cancel(boolean mayInterruptIfRunning)
//取消任务执行
public boolean cancel(boolean mayInterruptIfRunning) {
//条件一:state == NEW成立,表明当前任务处于运行状态或处于线程池任务队列中
//条件二:UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)条件成立;说明设置状态为INTERRUPTING 或CANCELLED成功
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
//若mayInterruptIfRunning为true
if (mayInterruptIfRunning) {
try {
//执行当前FutureTask的线程,有可能现在是null,是null的情况是:当前任务在队列中,还没有线程获取到它
Thread t = runner;
//条件成立,说明当前线程runner,正在执行task
if (t != null)
//给当前执行task的线程一个中断信号,若程序响应中断,则会走中断逻辑,否则程序啥也不做
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
//唤醒所有调用get()方法的等待线程
finishCompletion();
}
return true;
}
3.2.6 get()
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
这里的get方法主要做了两件事:
1.如果当前任务状态值小于等于COMPLETING,调用awaitDone方法进行入队或出队操作;
2.调用report方法返回任务执行结果。
3.2.7 get(long timeout, TimeUnit unit)
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
//判断传入参数是否为空,为空抛出异常
if (unit == null)
throw new NullPointerException();
int s = state;
//调用awaitDone方法传入指定时间,若达到指定时间状态仍未改变,则抛出异常
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
3.2.8 set(V v)
protected void set(V v) {
//使用cas方式设置当前线程状态为 完成中
//此处也是有可能失败的,外部线程直接在set执行cas之前将任务取消了(概率较小)
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//若设置线程状态成功,设置结果给outcome
outcome = v;
//将结果设置给outcome后,设置任务状态为NORMAL,任务正常结束
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
3.2.9 setException(Throwable t)
protected void setException(Throwable t) {
//调用cas设置当前任务状态为COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
//调用cas设置当前任务状态为EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
3.2.10 run()
public void run() {
//条件一:state != NEW,判断当前是否已经执行或被取消
//条件二:UNSAFE.compareAndSwapObject利用cas判断当前线程是否被其它线程抢占,若抢占,则返回true
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
//执行到此处,表明任务状态一定为NEW,并且当前线程获取锁成功
try {
//此处callable表示我们自己实现的任务
Callable<V> c = callable;
//c != null
// state == NEW 此处判断是为了防止外部线程cancel任务
if (c != null && state == NEW) {
V result;
// ran == true,表明程序call()方法成功执行,未抛出异常
// ran == false,表明程序call()方法执行异常,抛出异常
boolean ran;
try {
//调用call()方法执行并获取返回结果
result = c.call();
ran = true;
} catch (Throwable ex) {
//执行call()方法异常,设置result为null,调用setException方法设置state状态
result = null;
ran = false;
setException(ex);
}
//call()方法执行成功,调用set方法设置结果,释放资源
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
//判断线程状态是否中断,若中断,则抛出异常
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
3.2.11 runAndReset()
//这个方法与run()方法类似,区别在于调用完成之后,会重置所有状态值(包括结果对象callable、当前状态s等),这里不再赘述
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,null,Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
runner = null;
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}
3.2.12 handlePossibleCancellationInterrupt(int s)
//这个方法的主要作用就是判断状态为INTERRUPTING的话,释放CPU资源
private void handlePossibleCancellationInterrupt(int s) {
//若传入状态为INTERRUPTING且当前任务状态为INTERRUPTING,调用Thread.yield()释放CPU资源
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield();
}
3.2.13 handlePossibleCancellationInterrupt(int s)
private void finishCompletion() {
// assert state > COMPLETING;
//q指向等待队列头节点
for (WaitNode q; (q = waiters) != null;) {
//调用cas设置waiters为null是因为怕外部线程使用cancel取消当前任务的同时也会触发finishCompletion方法
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
//将当前节点thread对象赋值给t
Thread t = q.thread;
// 判断t是否为空,不为空表示当前线程在等待任务结果,将thread对象设为空(GChelp GC),同时调用upark方法唤醒当前节点对应的线程
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
//将q的下一个节点赋给next对象
WaitNode next = q.next;
//如果next对象为空,表明队列已经达到末尾,利用break跳出循环
if (next == null)
break;
//如果next不为空,先将q.next指向的内存释放,再将next重新赋值给q,开始下一轮循环
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
//将callable 置为空,help GC
callable = null; // to reduce footprint
}
3.2.14 awaitDone(boolean timed, long nanos)
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
//计算超时时间(精确到纳秒)
final long deadline = timed ? System.nanoTime() + nanos : 0L;
//引用当前线程waitNode对象 封装成WaitNode 对象
WaitNode q = null;
//当前线程waitNode对象是否 入队
boolean queued = false;
for (;;) {
//如果线程被中断,从等待队列中移除该任务,同时抛出异常
//此条件成立,说明当前线程被其他线程以中断方式唤醒
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
//s > COMPLETING表明任务可能被已完成(还未设置成NORMAL状态)或者任务被取消
int s = state;
if (s > COMPLETING) {
//条件成立,表明已经为当前线程创建过node了,将q.thread == null用来helpGC
if (q != null)
q.thread = null;
return s;
}
//若状态为COMPLETING,则说明任务已完成,正在向NORMAL状态切换,此时释放CPU执行权
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
//第一次自旋,如果q为空,表示当前线程还未创建WaitNode对象,此时为当前线程创建waitNode对象
else if (q == null)
q = new WaitNode();
//判断是否在等待队列中,若不在,利用CAS将当前等待节点置于队列首部
//第二次自旋,当前线程已经创建了WaitNode对象,但是node对象还未入队
else if (!queued)
//CAS方式设置waiters引用指向当前线程node,成功的话返回true,否则表明其它线程快一步执行该操作
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
//判断是否设置超时等待
//第三次自旋,
else if (timed) {
//计算结束时间与当前时间差,若结果小于0,则从等待队列中移除该等待线程,同时返回状态
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
//按照传入时间阻塞当前线程,时间到达自动解锁
LockSupport.parkNanos(this, nanos);
}
//永久挂起,直至线程获取到计算结果(其它线程唤醒或当前线程被中断)
//当前get操作的线程就会被park,线程状态会变成WAITTING状态,线程会休眠
else
LockSupport.park(this);
}
}
3.2.15 removeWaiter(WaitNode node)
private void removeWaiter(WaitNode node) {
if (node != null) {
node.thread = null;
//自旋
retry:
for (;;) { // restart on removeWaiter race
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
if (q.thread != null)
pred = q;
else if (pred != null) {
pred.next = s;
if (pred.thread == null) // check for race
continue retry;
}
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s))
continue retry;
}
break;
}
}
}
这个方法的作用是:移除超时或已经被中断的等待节点。 这段代码的主要流程如下图所示:
4.小结
1.FutureTask实现了Runnable和Future接口,它是带有状态和结果的任务,所有的操作都是围绕着任务状态展开的;
2.如果任务状态不为NEW,表明任务已经完成或已经终止;
3.任务状态的切换过程大概如下四种,如下图所示:
4.callable的启动方式主要有两种,一种是利用FutureTask的构造方法,将callable作为参数传入;另一种调用submit方法提交到
线程池中;
5.底层还是使用了CAS完成一系列加锁和状态更改操作。
5.参考文献
1.https://www.bilibili.com/video/BV13E411N7pp?spm_id_from=333.1007.top_right_bar_window_history.content.click
2.https://juejin.cn/post/7033201440579878943
3.https://juejin.cn/post/6944572513896628238#heading-8