java多线程高级-FutureTask与Callable(八)
平时在工作中一般用到的多线程为:一种是直接继承Thread,另外一种就是实现Runnable接口,但是有时我们希望多线程执行完后返回结果,那就使用FutureTask与Callable。
FutureTask
从图中可以看出,FutureTask只是一个实现了Runnable的一个普通类,没什么特别的,唯一多了一个Future接口的实现就是。
Future 是一个接口,只是在Runnable的基础上,定义了几个扩展动作而已,至于你怎么实现,完全可以自定义,当然你也可以使用官方的FutureTask实现类来直接获取返回值。
Future(转)
Future对象本身可以看作是一个显式的引用,一个对异步处理结果的引用。由于其异步性质,在创建之初,它所引用的对象可能还并不可用(比如尚在运算中,网络传输中或等待中)。这时,得到Future的程序流程如果并不急于使用Future所引用的对象,那么它可以做其它任何想做的事儿,当流程进行到需要Future背后引用的对象时,可能有两种情况:
- 希望能看到这个对象可用,并完成一些相关的后续流程。如果实在不可用,也可以进入其它分支流程。
- “没有你我的人生就会失去意义,所以就算海枯石烂,我也要等到你。”(当然,如果实在没有毅力枯等下去,设一个超时也是可以理解的)
对于前一种情况,可以通过调用Future.isDone()判断引用的对象是否就绪,并采取不同的处理;而后一种情况则只需调用get()或get(long timeout, TimeUnit unit)通过同步阻塞方式等待对象就绪。实际运行期是阻塞还是立即返回就取决于get()的调用时机和对象就绪的先后了。
简单而言,Future模式可以在连续流程中满足数据驱动的并发需求,既获得了并发执行的性能提升,又不失连续流程的简洁优雅。
但是Futrue模式有个重大缺陷:当消费者工作得不够快的时候,它会阻塞住生产者线程,从而可能导致系统吞吐量的下降。所以不建议在高性能的服务端使用。
Future定义了几个方法:
- cancel方法用来取消任务,如果取消任务成功则返回true。
- isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
- isDone方法表示任务是否已经完成,若任务完成,则返回true;
- get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。 也就是说Future提供了三种功能:
1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
如果使用了Future中的get会产生阻塞,这里有点类似CountDownLatch一样,你去田里面干活,我在家做饭,要吃饭的时候我等你一起吃饭。
注意:只有在调用Future中的get时才会阻塞线程,所有对于主线程来说,越晚调用get获取返回值越有利。
自己实现Future
看了上面的理论,在看了下源码,发现原来在多线程下获取返回结果是这样的。知道了原理之后为什么不自己实现一个FutureTask呢?
FutrueTaskDome为自己简单实现的一个线程返回结果的类。
package cn.thread.first.callable.myself;
import java.util.concurrent.*;
import java.util.concurrent.locks.LockSupport;
public class FutureTaskDome<V> implements RunnableFuture<V> {
private Callable<V> callable;
private Object outcome;
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 Thread thread = Thread.currentThread();
public FutureTaskDome(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
if (state != NEW) {
return;
}
V result;
try {
result = callable.call();
System.out.println("result==" + result);
} catch (Exception e) {
e.printStackTrace();
result = null;
setException(e);
}
set(result);
finishCompletion();
}
protected void set(V v) {
//UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)
System.out.println("set==1="+state);
state = COMPLETING;//运行中
outcome = v;
state = NORMAL;//运行完毕
System.out.println("set==2="+state);
}
private void finishCompletion() {
Thread t = thread;
if (t != null) {
thread = null;
System.out.println("unpark.....");
LockSupport.unpark(t);
}
}
protected void setException(Throwable t) {
state = COMPLETING;//运行中
System.out.println("setEx" + state);
outcome = t;
state = EXCEPTIONAL;//运行完毕
}
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
public boolean isCancelled() {
return false;
}
public boolean isDone() {
return state != NEW;
}
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING) {
System.out.println("get1==" + s);
for (;;){
s = state;
System.out.println("for==" + s);
if (s > COMPLETING) {
break;
}else if (s == COMPLETING){
Thread.yield();
}
LockSupport.park();
}
}
System.out.println("get=report==");
return report(s);
}
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
private V report(int s) throws ExecutionException {
System.out.println("report==="+s+",outcome="+outcome);
Object x = outcome;
if (s == NORMAL)
return (V) x;
throw new ExecutionException((Throwable) x);
}
}
DomeMain主线程
package cn.thread.first.callable.myself;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
class DomeService implements Callable<String> {
private String name;
public DomeService(String name) {
this.name = name;
}
public String call() throws Exception {
System.out.println("---" + name);
return "我的名字叫:" + name;
}
}
public class DomeMain {
public static void main(String args[]) {
Callable<String> callable = new DomeService("bww");
FutureTaskDome<String> futureTaskDome = new FutureTaskDome<String>(callable);
Thread thread = new Thread(futureTaskDome);
thread.start();
try {
//Thread.sleep(2000);
String stringFuture = futureTaskDome.get();
System.out.println(stringFuture);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
先来看下结构图,这样就明白了。
解说:
- FutureTaskDome实现了Runnable,所以在使用thread.start()会开启一个子线程来处理FutureTaskDome中run()方法。
主线程中操作了futureTaskDome.get()相当于同步调用了一个方法而已,而get里面有一个判断逻辑,如果state<2就会阻塞,这里阻塞的是当前线程,当前线程是主线程啊,所以主线程就被阻塞了。
图中main方法里面的1,2步是异步的哦,1,2步没有任何关联,start()会开启一个线程,单独去处理run()方法。假设没有调用start开启一个线程,而是直接执行futureTaskDome.run(),方法呢。是不是感觉就很简单了,第一步操作了run,run方法里面处理完毕任务,然后执行第二步顺利拿到结果完事。
这里想多说两句,我们平时的变成时顺序执行,很好理解,而thread.start()方法开启了一个单独的线程去执行了run方法。thread.start()无非就是我一个人盖房子太慢了,执行thread.start()找了了一个帮手,我们一起盖房子,我们两个同时干活。所以这里的start()后,只是说futureTaskDome.run()和主线程一起执行了而已,没有什么好特别的。
DomeService类实现了Callable,DomeService里面的run()方法才是我们真正要处理的任务。但并没有为它开启一个单独的线程去执行,而是直接调用它的run()方法。真正单独开启了一个线程的是FutureTaskDome这个对象,而FutureTaskDome这个对象的run方法再调用DomeService里面的run()方法去处理任务。
FutureTaskDome就好像一个代理一样。
futureTaskDome.get() 这个里面有一个判断,state<2时,阻塞当前线程。谁是当前线程呢?那就是主线程了,因为是主线程调用的get方法。
LockSupport.park();这个在重温一遍,哪个线程调用它,它就阻塞哪个线程。这里是主线程调用了它,所以阻塞了主线程。
private Thread thread = Thread.currentThread();这里的当前线程怎么算呢?谁是实例化了它,Thread.currentThread()就是它了。这里是主线程里面实例化了它,所以thread变量就是主线程。
如果把Thread.currentThread()写到futureTaskDome.run()里面或者callable.run()里面,代表的就是子线程futureTaskDome了,因为futureTaskDome.run()是开启了一个新的线程,而这个新的线程又调用了callable.run()方法,所以他们是一条线的上的。
这里有两个线程:圈圈标记的1,2是两个线程。2是1的子线程。
“问题” :为什么子线程2里面run()改变了state=2,而1线程get()的时候能读到state=2呢?
因为线程1,2操作的是同一个对象futureTaskDome。哈哈哈哈
Callable
看到好多文章拿Callable与Runable来做对比,或者说问一些他们之间的区别,还长篇大论的讨论,其实他们就是一个接口而已,仅仅一个接口而已,没什么别的了。
Runnable是java jdk原生定义的,如果实现了Runnable,那么在Thread.start()就会创建一个新的线程来调用Runaable实现类的run方法。仅此而已。
而Callable则是rt.jar里面的扩展接口,统一接口规范而已。
源码解读
其实上面也是参照源码实现的,这里讲解源码已经不是很必要了,但是也解说一下吧。
Callable,Future,都是接口没啥将的,主要是将一下实现类FutureTask。
成员变量:
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;//中断结束状态
//这个就好比一个代理,接受外面传递进来的callable,然后这里代理它执行run方法。
private Callable<V> callable;
//用来阻塞调用放的线程用的,谁调用我,我就阻塞谁。下面会有讲解。
private volatile Thread runner;
//调用get方法获取返回值时,一个阻塞内部类
private volatile WaitNode waiters;
//实例化的时候
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
//开启线程调用run方法时:
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 {
//代理执行外面传递进来的callable的call方法。
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
//返回线程内部异常信息。
setException(ex);
}
//调用set设置返回值,解锁阻塞线程,更改状态
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);
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
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
}
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)这个是个调用本地方法的cas操作。将COMPLETING变量值设置给state,如果设置成功返回true,如果设置失败返回false。这里怎么就操作了state了呢?看下面,这里是通过指针偏移来操作的,具体不是很懂,太底层了,直到它是设置state的值就好了。CAS前面文章也讲过了,这里不再赘述。
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);
}
}
上面是整个子线程里面的操作,就是执行run任务,然后设置状态,没了。那么现在来看看主线程调用xxx.get()方法获取返回值的时候干了什么事。
public V get() throws InterruptedException, ExecutionException {
int s = state;
//是不是和我上面写的一摸一样,看看是否还在执行,如果s<=1则表示线程正在执行,进入等待中。
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
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();
}
int s = state;//再次获取state的状态
if (s > COMPLETING) {//在判断一次。
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) //如果s==1表示正在执行,调用yield,让其他线程先执行。注意:yield只是客气一下,并不一定会产生实质性结果,就是说并不一定会真的让出CPU,让其他线程占用。这个和暂停1毫秒差不多,我的理解啊。
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;
}
//get的时候是否设置了时间,设置了时间,过了这个时间则自动解锁不在等待返回结果。
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);//阻塞调用get()方法的线程。这个park是阻塞当前调用get的线程和this参数无关哦。
}
}
参考
http://blog.youkuaiyun.com/u010412719/article/details/52137262
http://zha-zi.iteye.com/blog/1408189