You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java线程执行getFactors中途挂起,缓存更新后操作对象的疑问

不可变类OneValueCache的线程安全疑问解答

核心结论

线程A恢复执行后,操作的是更新前的cache对象。

原因分析

当线程A调用cache.getFactors(...)时,第一步会把当前cache引用指向的对象实例(也就是更新前的OneValueCache实例)加载到自己的工作内存中。后续整个方法的执行过程,都是基于这个已经获取到的实例进行操作。

即使线程B在此时更新了TestImmutable类中持有的cache引用(指向一个新的OneValueCache实例),线程A工作内存里的引用副本不会被自动同步,所以它只会继续操作最初拿到的那个旧实例。

这也是不可变类线程安全的关键之一:一旦实例创建完成,内部状态就不会改变,不管哪个线程持有该实例,操作时都不会出现状态不一致的问题;而引用的更新是原子操作,新线程会直接拿到新的实例,旧线程则继续操作旧实例,两者互不干扰。

可复现场景的代码示例

通过CountDownLatch可以精准控制线程挂起和恢复的时机,复现你描述的场景:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 不可变类OneValueCache
class OneValueCache {
    private final Integer number;
    private final Integer[] factors;

    public OneValueCache(Integer number, Integer[] factors) {
        this.number = number;
        this.factors = factors.clone(); // 防御性拷贝保证不可变性
    }

    public Integer[] getFactors(Integer number) {
        if (this.number == null || !this.number.equals(number)) {
            return null;
        }
        try {
            // 模拟方法执行中途暂停,等待唤醒信号
            TestImmutable.latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        }
        return factors.clone();
    }
}

public class TestImmutable {
    public static CountDownLatch latch = new CountDownLatch(1);
    private volatile OneValueCache cache = new OneValueCache(10, new Integer[]{2, 5});

    public void updateCache(int number, Integer[] factors) {
        cache = new OneValueCache(number, factors);
        System.out.println("线程B已更新cache为number=" + number);
    }

    public void queryFactors(int number) {
        System.out.println("线程A开始查询number=" + number);
        Integer[] factors = cache.getFactors(number);
        if (factors != null) {
            StringBuilder sb = new StringBuilder();
            for (int factor : factors) {
                sb.append(factor).append(",");
            }
            System.out.println("线程A查询结果:" + number + "的因数是" + sb.deleteCharAt(sb.length()-1));
        } else {
            System.out.println("线程A未找到number=" + number + "的因数");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestImmutable test = new TestImmutable();
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // 启动线程A执行查询
        executor.submit(() -> test.queryFactors(10));

        // 等待线程A进入getFactors的暂停点
        Thread.sleep(1000);

        // 启动线程B更新cache
        executor.submit(() -> test.updateCache(20, new Integer[]{2, 4, 5})).get();

        // 唤醒线程A继续执行
        latch.countDown();

        executor.shutdown();
    }
}

代码输出说明

运行后会输出:

线程A开始查询number=10
线程B已更新cache为number=20
线程A查询结果:10的因数是2,5

可以看到,线程A最终返回的是旧cache中10的因数,证明它操作的是更新前的实例。

内容的提问来源于stack exchange,提问作者Unicorn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 22:57:32