使用 AtomicReference 进行惰性初始化

问题描述 投票:0回答:1

这样可以安全地完成惰性初始化吗?我们发生了争执,一位同事声称某些第一个线程可能会失败,并且他们会看到空值。

public class TestAtomicReference {
    static final AtomicReference<String> A = new AtomicReference<>();

    public static void main(String[] args) throws Exception {

        Callable<Integer> callable = () -> {
            lazyInit();
            return 0;
        };

        ExecutorService executorService = Executors.newFixedThreadPool(50);

        for (int i = 0; i < 1000; i++) {
            A.set(null);
            List<Future<Integer>> futures = executorService.invokeAll(Collections.nCopies(50, callable));
            futures.forEach(f -> {
                try {
                    f.get();
                } catch (Exception ignore) {
                }
            });
        }

        executorService.shutdown();
        executorService.awaitTermination(10, TimeUnit.SECONDS);
    }

    private static void lazyInit() {
**        if (A.get() == null) {
            A.compareAndSet(null, costlyGetValue());
        }
**
        if (A.get() == null) {
            System.out.println("INIT FAILURE!");
        }
    }

    private static String costlyGetValue() {
        return "A";
    }
}

get 和compareAndSet 的分离是为了避免每次获取成本高昂。 这个测试代码永远不会失败...... 谢谢

java lazy-initialization atomicreference
1个回答
0
投票

看起来您的代码确实可以工作,但这不是延迟初始化的好方法,因为您可能有许多线程评估

a.get() == null
。因此,除了一个初始调用之外的所有调用都可能评估
costlyGetValue()
,并且这些昂贵调用的除一个结果之外的所有结果都将被丢弃。例如,当操作使用大量 I/O 或数据库访问时,这可能会导致系统出现严重问题。

您可以通过更改这样的测试代码来进行演示,这可能会显示最多 50 个线程调用

costlyGetValue()
:

private static String costlyGetValue() {
    System.out.println(Thread.currentThread()+" costlyGetValue()");
    // if it doesn't show, add sleep here too
    return "A";
}

相反,您可以更改由持有者类处理的延迟初始化。 JVM 将仅初始化持有者类一次,仅调用一次

costlyGetValue()
。每当您想读取该值时,都可以使用
Holder.getInstance()

static class Holder {
    public static final Holder INSTANCE = new Holder();
    private final String costlyValue;
    private Holder() {
        this.costlyValue = costlyGetValue();
    }
    public static String getInstance() {
        return INSTANCE.costlyValue;
    }
}
private static void lazyInit() {
    if (Holder.getInstance() == null) {
        System.out.println("INIT FAILURE!");
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.