多个线程调用FutureTask导致我们写的Callable的call方法只执行了一次(原因:源码详解)(解决问题:详解)

首先我们知道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不为NEWFutureTask的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; //任务线程已中断

想必,现在你应该很透彻了。

好了,最后说一句,人家是故意这样开发的,其目的就是为了提高效率,因为对于返回值总是不变的,我们总是要考虑把他缓存一下,下一次的时候,直接把值返回出去,就不用再进行计算了,大大的提高了效率。

希望对你有帮助!

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值