首先我们知道FutureTask实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和Future接口。
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
所以他就是一个适配类,负责在new Thread()中可以使用Callable。用的适配器设计模式的思想。
好,我们看下面这段代码:
public class FutureTaskTest {
public static void main(String[] args) throws Exception {
FutureTask<String> task=new FutureTask<>(()->{
System.out.println("run...");
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "小贱哥哥";
});
new Thread(task).start();
new Thread(task).start();
System.out.println(task.get());
}
}
运行结果:
run...
Thread-0
小贱哥哥
到这里,我们发现了一个问题,我明明用了两个线程去执行,为什么只输出一次结果?
这是怎么回事呢?
其实问题很简单,我们先看看我们写的代码依次对照源码进行解析:
FutureTask<String> task=new FutureTask<>(()->{
System.out.println("run...");
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "小贱哥哥";
});
我们new 了一个FutureTask,对应的FutureTask内部执行了:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
我们注意一下:this.state = NEW; 和 this.callable = callable;
然后我们就开始执行了:
new Thread(task).start();
new Thread(task).start();
我们先思考一个问题,既然FutureTask间接的实现了Runnable接口,所以FutureTask里面肯定实现重写了run方法,所以线程启动执行,还是走的FutureTask的run方法,好,我们来看看他的run方法:
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
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);
}
} 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);
}
}
我们看到只有当state == NEW和callable != null时,才会有可能执行到call方法( result = c.call(); )。
(这个c.call();方法的内容就是我们之前写的内容)(这里用了lambda表达式):
FutureTask<String> task=new FutureTask<>(()->{
System.out.println("run...");
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "小贱哥哥";
});
好,到这,我们继续看,因为我们的程序是属于正常运行到结束的,所以会继续往下面走,一直走到set方法(还是上面的run方法):
public void run() {
......
if (ran)
set(result);
......
}
那我们来看看set方法:
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}
发现STATE.setRelease(this, NORMAL);他把state改为了NORMAL。
继续再set方法中往下看到finishCompletion方法,点进去,我们发现:
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (WAITERS.weakCompareAndSet(this, 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
}
这个方法最后把我们的callable改为了null。
好,到这里一切就清楚了。
第一个线程执行的时候,FutureTask的state已经不是NEW了,callable变为null了。又因为我们两个线程用的FutureTask的同一个实例,所以第二个线程执行的时候,就直接到了这里return了:
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
.......
}
所以这也就是为什么只执行了一次的原因。
我们不想这样,怎么修改这种情况呢?
其实就是想办法让state的值为NEW,callable值不为null
方法一:再重新new一个FutureTask:
((因为重新new的时候,他会走FutureTask的构造方法把state改为NEW,并重新赋值callable)前面已经说了这个东西:this.state = NEW; 和 this.callable = callable;)
public class FutureTaskTest {
public static void main(String[] args) throws Exception{
final int[] i={0};
Callable callable=()->{
System.out.println("run...");
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "小贱哥哥"+(++i[0]);
};
FutureTask<String> task1=new FutureTask<>(callable);
FutureTask<String> task2=new FutureTask<>(callable);
new Thread(task1).start();
new Thread(task2).start();
System.out.println(task1.get());
System.out.println(task2.get());
}
}
这次的结果就是:
run...
Thread-1
run...
Thread-0
小贱哥哥2
小贱哥哥1
方法二:先思考:FutureTask的run()方法因为FutureTask的state不为NEW和FutureTask的callable为null导致的,我们有没有什么办法,把这两个值改了呢?我们看源码:
private volatile int state;
private Callable<V> callable;
发现都是私有的,我们没法直接调用修改,怎么办呢?(答案:利用反射)
public class FutureTaskTest {
public static void main(String[] args) throws Exception {
final int[] i = {0};
FutureTask<String> task=new FutureTask<>(()->{
System.out.println("run...");
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(1);
return "小贱哥哥"+(++i[0]);
});
new Thread(task).start();
Field callable = task.getClass().getDeclaredField("callable");
callable.setAccessible(true);
Object call = callable.get(task);
System.out.println(task.get());
Field state = task.getClass().getDeclaredField("state");
state.setAccessible(true);
state.set(task,0);
callable.set(task,call);
new Thread(task).start();
System.out.println(task.get());
}
}
结果:
run...
Thread-0
小贱哥哥1
run...
Thread-1
小贱哥哥2
你可能会对上面的state.set(task,0);产生疑惑,为什么要设置成0呀?
所以我们还是得看源码:
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; //任务线程已中断
想必,现在你应该很透彻了。
好了,最后说一句,人家是故意这样开发的,其目的就是为了提高效率,因为对于返回值总是不变的,我们总是要考虑把他缓存一下,下一次的时候,直接把值返回出去,就不用再进行计算了,大大的提高了效率。
希望对你有帮助!