这样可以安全地完成惰性初始化吗?我们发生了争执,一位同事声称某些第一个线程可能会失败,并且他们会看到空值。
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 的分离是为了避免每次获取成本高昂。 这个测试代码永远不会失败...... 谢谢
看起来您的代码确实可以工作,但这不是延迟初始化的好方法,因为您可能有许多线程评估
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!");
}
}