共计 8374 个字符,预计需要花费 21 分钟才能阅读完成。
Runable 与 Callable 的区别,据说是高频面试题,什么样的答案才会让面试官称心呢?
所有 java 程序员都晓得的答案是基于:
public interface Runnable {public abstract void run();
}
public interface Callable<V> {V call() throws Exception;
}
从 Runable 和 Callable 接口的定义咱们就能晓得的是:
Runable 接口的 run 办法不返回后果,不容许他的实现类抛出异样。
Callable 接口的 call 办法能够返回后果,并且容许抛出异样。
再和 Thread 关联一下,能够补充:
- 新创建一个线程 Thread 执行 Runable 能够实现异步调用,调用线程在创立新线程执行 Runable 工作之后能够立刻取得执行权,不影响后续工作的执行
- Callable 能够被封装在 FutureTask(Runable 的实现类)中从而被 Thread 调用,这种状况下调用线程能够通过 FutureTask.get() 办法获取到 Callable 接口的 call 办法的返回值,然而 FutureTask.get() 办法是阻塞调用,直到线程执行实现才会有后果返回,所以调用线程如果执行了 FutureTask.get() 的话,后续工作就必须期待
如果单从这个问题来讲,答案应该就差不多残缺了。然而如果面试官老爷还想理解细节,那咱们就必须要对 FutureTask 甚至 Executor 做一个深刻的理解。
其实咱们并不是为了答复这个面试问题才去钻研这部分内容的,而是因为接下来筹备认真学习、钻研一下线程池、连接池等池化技术,而 FutureTask、Executor 是线程池技术的根底,所以咱们就须要首先搞清楚 FutureTask 和 Executor 的底层原理,顺道兴许正好能把这个高频 java 根底面试题答复分明。
进入正题。明天的内容包含:
- Thread:概念性的简略阐明,不波及启动、进行、中断、挂起等等状态转换机制
- FutureTask 源码剖析
Thread 简要阐明
Java 程序是以线程来运行的,JVM 容许一个 java 利用启动多个线程,当咱们的程序以 main 办法启动之后,其实 JVM 就启动了一个线程来执行程序。
每一个 java thread 都有优先级,在资源抢夺过程中高优先级线程比低优先级线程更容易取得执行权。线程能够被标注为是否为守护线程,默认状况下,新创建的线程和创立他的线程领有雷同的优先级,以后仅当创立线程是守护线程的时候,被创立的新线程能力是守护线程。
JVM 启动的时候,通常会创立一个非守护线程(主线程),主线程会继续运行直到:
- Runtime 的 exit 办法执行,或者利用抛出的异样始终被传递到主线程的 run 办法
- 所有非守护线程执行结束:run 办法失常执行实现或捕捉到异样
有两种创立线程的形式:
- 创立一个 Thread 类的子类并笼罩他的 run 办法,而后调用子类对象的 start 办法,最终子类的 run 办法会被调用执行
- 创立 Runable 接口的实现类,作为参数传递给 Thread 的结构器创立 Thread 并执行 start 办法,最终 Runable 实现类的 run 办法会被调用执行
常见的是第 2 种办法。
FutureTask
先看一下 FutureTask 的类图:
第一点须要晓得的是,FutureTask 是 Runable 接口的实现。
同时他还实现了 Future 接口,所以咱们须要首先简略理解一下 Future,看一下 JavaDoc:
体现异步工作的执行后果,提供办法查看工作是否实现、期待异步工作执行实现、并获取执行后果。执行后果只能通过 get 办法获取,只有工作实现后能力获取到,否则会阻塞期待工作实现后返回后果。通过 cancel 办法能够勾销工作,Future 还提供了一些其余办法能够帮忙获取到工作是失常完结、还是被勾销掉了,一旦工作执行实现,则不再容许勾销。如果你只是想通过 Future 来实现工作能够被勾销这一个性、而并不在意工作的返回后果,让 Future 的工作返回 null 即可。
所以咱们晓得 Future 是为了获取到异步工作的执行后果而生的。
FutureTask 的工作
FutureTask 蕴含一个叫 callable 的 Callable 成员变量,就是 FutureTask 的底层工作,咱们首先看一下这个底层工作是怎么被创立或者初始化的。
先来看 FutureTask 对象的创立,FutureTask 提供了两个构造方法:
public FutureTask(Callable<V> callable) {if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
其中一个构造方法传入 Callable 对象,间接赋值给成员变量 callable,设置 state=NEW,简单明了。
另外一个构造方法传入 Runable 对象,以及一个 result 对象,FutureTask 首先调用 Executors 的 callable 办法把传入的 Runable 对象包装成一个 Callable 对象,而后把包装好的 Callable 对象赋值给成员变量 callable,设置 state=NEW,完结。
所以咱们晓得,Future 的底层工作只能是 Callable 对象,然而他也能够接管 Runable 对象,他本人外部负责将 Runable 封装成 Callable,对调用方来说是通明的。
Runable 封装为 Callable
Runable 是通过 Executors 的 callable 办法封装为 Callable 的,最终生成了一个 RunnableAdapter 对象:
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {task.run();
return result;
}
}
从源码能够看到 RunnableAdapter 实现了 Callable 接口,将 Callable 的 call 办法调用转换为对 Runable 对象的 run 办法调用,call 办法的返回后果实际上是调用方传进来的。
Runable 对象被这样封装之后,也就具备了通过 Thread 异步调用之后能够通过 Future 的 get 办法阻塞获取返回的个性了,咱们须要晓得,阻塞是真的,这个返回后果 result 其实是你在创立 FutureTask 对象的时候传进去的那个 result,工作执行实现后原样返回。
FutureTask 的状态
FutureTask 的状态 state:
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;
对象创立后状态为 NEW,工作被线程调起后变更为 COMPLETING,工作失常执行实现后为 NORMAL,执行过程中抛出异样为 EXCEPTIONAL,工作被勾销为 CANCELLED,工作被中断为 INTERRUPTING->INTERRUPTED。
FutureTask 的线程安全性
咱们须要留神一点,FutureTask 位于 java.util.concurrent 包下,通过 CAS 及 UNSAFE 来确保线程安全线。
提前理解一下 FutureTask 的另外一个成员变量 runner,其实就是执行 callable 工作的线程,初始化为 null,FutureTask 构造函数也没有初始化过,所以 FutureTask 对象创立之后应该也是 null:
private volatile Thread runner;
咱们在进行后续代码剖析之前须要首先理解一下 FutureTask 定义的几个 UNSAFE 对象:
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
static {
try {UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiters"));
} catch (Exception e) {throw new Error(e);
}
}
stateOffset:对应状态 state 的内存地址
runnerOffset:对应线程 runner 的内存地址
waitersOffset:对应阻塞对象 waiters 的内存地址
伪代码 Demo
为了不便形容,咱们以上面的伪代码举例:
// 假如 MyCallable 是咱们本人的工作
Callable callable=new MyCallable();
FutureTask f1=new FutureTask(callable);
Thread t1= new Thread(f1);
t1.start();
Object o = f1.get();
...
其中 MyCallable 就是咱们本人的 Callable 实现类,篇幅关系,就不贴出所有的代码了。
这个例子中会波及到两个线程,一个是主线程咱们称之为 tmain,另外一个是执行 FutureTask f1 的线程咱们称之为 t1。
FutureTask 的 run 办法
通过 t1.start 启动线程 t1 后,FutureTask 的 run 办法会被调用。
FutureTask 的 run 办法代码其实不长,然而为了容易了解,咱们还是分段剖析:
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
首先是判断以后状态如果不等于 NEW 则返回,第二句 UNSAFE.compareAndSwapObject 办法的意思是:间接通过 JVM 查看 runner 是否是 null,如果是 null 的话赋值为以后 Thread,不是 null 则间接返回。
这个时候 runner 就是工作执行线程 t1 了。
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);
}
而后通过调用 callable 的 call 办法执行工作,返回赋给 result 对象。如果执行胜利的话,调用 set 办法,产生异样则调用 setException(ex) 进行异样解决。
咱们重点看一下 set(result) 办法:
protected void set(V v) {if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();}
}
状态 state 首先批改为 COMPLETING,执行后果 result 赋值给 outcome,为 get 办法的返回做筹备。之后 state 批改为最终状态 NORMAL,最初调用 finishCompletion() 办法。
FutureTask#get 办法
get 办法是 FutureTask 的灵魂,如果工作尚未执行实现则 get 办法始终阻塞直到实现。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
如果状态小于等于 COMPLETING 则调用 awaitDone 办法,否则调用 report 办法返回后果。
那肯定是 awaitDone 办法实现阻塞期待的。
FutureTask#awaitDone 办法
Awaits completion or aborts on interrupt or timeout.
期待工作实现,或者因为中断、超时后放弃
首先 for 循环自旋:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {if (Thread.interrupted()) {removeWaiter(q);
throw new InterruptedException();}
如果以后线程被中断,从 watiers 队列开释本次的 WaitNode、抛出异样。
判断以后状态如果大于 COMPLETING 则开释以后期待队列 q 对应的 Thread 后,返回,否则如果状态等于 COMPLETING,阐明工作还没有实现(没到最终状态 NORMAL),通过调用 Thread.yield() 持续期待。否则,state 应该就小于 COMPLETING,阐明工作还在执行中,如果排队队列 q 为 null 的话创立队列 q =new WaitNode(); 否则,如果尚未排队(!queued)则 q 退出 waiters 队列。否则,调用 LockSupport.part 办法挂起以后线程期待唤醒。
int s = state;
if (s > COMPLETING) {if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
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;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
代码 show 完了,咱们接下来剖析一下这段代码的逻辑:
第一步,咱们须要首先明确一下这段代码执行的时候的“以后线程”是谁?
很显著,以后线程不是 t1, 而是咱们下面示例代码的主线程 tmain。
第二步,如果以后线程被中断,则从 waiters 中移出本次退出的 WaitNode 后抛出中断异样。
否则,如果状态变大于 COMPLETING,则将以后 WaitNode 的线程置空间接返回。
否则如果状态为 COMPLETING,则表明工作执行线程曾经实现工作的执行、FutureTask 尚未实现清场解决所以尚未达到最终状态,则调用 Thread.yield(); 持续期待。
否则,t1 线程还没有执行实现工作 FutureTask f1,创立一个期待对象 WaitNode,WaitNode 对象会持有以后线程 tmain:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
第三步,持续自旋,如果 FutureTask 状态依然小于 COMPLETING,则将 WaitNode 退出到期待队列 waiters 中。
第四步,持续自旋,如果 FutureTask 状态依然小于 COMPLETING,调用 LockSupport.park(this); 交出 tmain 线程的控制权,咱们晓得 park 办法后以后线程 tmain 会被挂起(处于 Waiting 状态或 sleep 状态),只能通过其余线程调用以后线程的 unpark 才会被唤醒。
这样咱们就很分明的晓得了 get 办法的逻辑:
- 如果 t1 线程曾经执行实现并且 FutureTask 曾经到了最终状态,get 办法能够立即返回
- 否则,如果 t1 线程曾经执行实现然而 FutureTask 还没有到最终状态(FutureTask 的开头工作没实现),通过调用 Thread.yield 办法期待 t1 线程将 FutureTask 执行到最终状态
- 否则,t1 线程没有执行实现,则创立 WaitNode 对象持有以后线程,并将 WaitNode 对象退出 waiters 队列;通过 LockSupport.park 将调用线程 tmain 挂起,期待唤醒
咱们当初的问题是要找到 tmain 怎么被唤醒?
FutureTask#finishCompletion 办法
咱们先看一下 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
}
从源码能够看出 finishCompletion 办法的逻辑:
- 通过原子操作从 waiters 中获取 WaitNode q
- 从 q 中获取其持有的线程 t,如果该线程不空的话,调用 LockSupport.unpark(t) 唤醒该线程
- q 的下一个节点 next 为空的话完结循环
- 否则,q 指向下一个节点
要害在第 2 步,大略的意思是说,我执行实现之后,所有那些因为期待我执行实现而主动挂起本人的哥们,都能够被唤醒了。
接下来就是须要验证一下 finishCompletion 办法在什么中央被调用:一个是 cancel 办法,通过 FutureTask 勾销工作;另外一个是 set(V result)办法, 在 run 办法胜利执行实现后;还有一个是 setException 办法,在 run 办法执行异样的时候。
水落石出!
小结
FutureTask 的次要代码逻辑就剖析实现了,对于 FutureTask 的底层逻辑应该也就有了一个深刻的理解了。这个时候如果回过头再来看 Runable 和 Callable 的区别的话,应该就会有更清晰的意识了。
Thanks a lot!
上一篇 Quartz – 集群 Cluster 的配置、failOver 原理