从源码分析:为什么两个FutureTask线程只输出一个结果?

文章探讨了FutureTask在Java中如何确保单线程执行的问题,指出A线程执行后会改变Callable和state,导致后续线程不执行call()。提供了解决方案:新建FutureTask实例或使用反射修改私有字段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

先上demo:

package callable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class callableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask<Integer> futureTask = new FutureTask<>(myThread);
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();
        Integer integer = futureTask.get();
        System.out.println(integer);
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("call");
        return 111;
    }
}

运行结果:

call
111

为什么只输出一次结果呢?

分析:
从FutureTask()的源码部分得知,一开始就将this.callable=callable;this.state=NEW;

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
}

FutureTask()是Runnable的子类,所以肯定重写了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);
        }
    }

第一个if判断是为了return的,暂时不管,继续往下看,发现只有当(callable!=null&&state==NEW),才会boolean ran/有可能执行call(),刚刚说了这俩条件都满足了,就接着往下走,发现 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();
        }
}

ok,他把state改成了normal,乘胜追击,继续看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; ,很明显,call()的2个执行条件全部都不满足了。
那就能解释了:💖A线程和B线程的FutureTask是同一个💖,都调用了class MyThread,当第一个线程执行时会改变callable和state,导致满足了run()的return,就不会执行后面的call()了。

public void run() {
        if (state != NEW ||
            !RUNNER.compareAndSet(this, null, Thread.currentThread()))
            return;
        try {
        ......

论坛上的解决办法:
①new 两个FutureTask()

		MyThread myThread = new MyThread();
        FutureTask<Integer> futureTask1 = new FutureTask<>(myThread);
        FutureTask<Integer> futureTask2 = new FutureTask<>(myThread);
        new Thread(futureTask1,"A").start();
        new Thread(futureTask2,"B").start();
        
        Integer integer1 = futureTask1.get();
        Integer integer2 = futureTask2.get();
        System.out.println(integer1);
        System.out.println(integer2);

②修改callable和state。但是如下图源码所示,callable和state都是私有的,无法直接获取

private volatile int state;
private Callable<V> callable;

那怎么办呢?反射
这里借用坛友的写法:

		Field callable = task.getClass().getDeclaredField("callable");  //反射获得callable
        callable.setAccessible(true);   //解除访问限制
        Object call = callable.get(task);
        System.out.println(task.get()); 
        Field state = task.getClass().getDeclaredField("state");    //反射获得state
        state.setAccessible(true);
        state.set(task,0);  //设置state值
        callable.set(task,call);    //设置callable值
        new Thread(task).start();
        System.out.println(task.get());

为什么state.set(task,0); :

private volatile int state;
    private static final int NEW          = 0;		//刚创建,0就是new
    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;

补充:官方这样设计是为了提高效率——缓存返回值,下次可以直接把值返回出去,不用计算

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值