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原理