如何为对象引用构建延迟初始化线程安全包装器
how to build a lazy initializing thread safe wrapper for an object reference
我想实现一个包装器 class。 class 唯一 public 面对的东西是:
- 一个构造函数,它采用逻辑来创建包装的实例 class。比如
Supplier<WrappedType>
,也许。
- 获取包装实例的方法class。
其行为遵循以下规则:创建包装 class 的逻辑可能会产生副作用,并且只能调用一次。而且,显然,getter 方法实际上应该始终 return 相同的包装 class 实例,这实际上应该是 运行 传递给构造函数的逻辑的结果.
我想我有这段代码可以满足我的要求,但我不确定如何测试它是否一定能正常工作,或者是否有更好的方法。
package foo;
import java.util.function.Supplier;
public final class ConcurrentLazyContainer<A> {
private Supplier<A> supplier;
private A value;
public ConcurrentLazyContainer(Supplier<A> supplier) {
this.supplier = supplier;
value = null;
}
public synchronized A get() {
if (value == null) {
value = supplier.get();
supplier = null;
}
return value;
}
}
是否仅使用 synchronized
就可以让我一路到达我想要的地方?也许我的字段也需要可变?
我写了一个测试来启动调用同一个包装器的新线程,但在我看来,supplier 并没有被多次调用,这很奇怪,因为我真的不明白为什么 volatile 不会被调用这里有必要。
问题的评论是正确的:如果你只在synchronized
方法中访问value
,那么你不需要它也是volatile
。但是,在某些情况下,您可以使用 double-checked locking.
来提高性能
public final class Lazy<T> {
private final Supplier<? extends T> initializer;
private volatile T value;
public Lazy(Supplier<? extends T> initializer) {
this.initializer = initializer;
}
public T get() {
T result = this.value;
if (result == null) {
synchronized (this) {
result = this.value;
if (result == null) {
this.value = result = this.initializer.get();
}
}
}
return result;
}
}
此代码基于 Effective Java and Java Concurrency in Practice 中显示的一些示例。请注意,此代码检查两次以查看 result
是否为 null
,一次在 synchronized
块之外,一次在内部。这样做的好处是,如果该值已经存在,则不需要同步。请注意,使用此策略,value
必须是 volatile
,因为它是在 synchronized
块之外访问的。
我想实现一个包装器 class。 class 唯一 public 面对的东西是:
- 一个构造函数,它采用逻辑来创建包装的实例 class。比如
Supplier<WrappedType>
,也许。 - 获取包装实例的方法class。
其行为遵循以下规则:创建包装 class 的逻辑可能会产生副作用,并且只能调用一次。而且,显然,getter 方法实际上应该始终 return 相同的包装 class 实例,这实际上应该是 运行 传递给构造函数的逻辑的结果.
我想我有这段代码可以满足我的要求,但我不确定如何测试它是否一定能正常工作,或者是否有更好的方法。
package foo;
import java.util.function.Supplier;
public final class ConcurrentLazyContainer<A> {
private Supplier<A> supplier;
private A value;
public ConcurrentLazyContainer(Supplier<A> supplier) {
this.supplier = supplier;
value = null;
}
public synchronized A get() {
if (value == null) {
value = supplier.get();
supplier = null;
}
return value;
}
}
是否仅使用 synchronized
就可以让我一路到达我想要的地方?也许我的字段也需要可变?
我写了一个测试来启动调用同一个包装器的新线程,但在我看来,supplier 并没有被多次调用,这很奇怪,因为我真的不明白为什么 volatile 不会被调用这里有必要。
问题的评论是正确的:如果你只在synchronized
方法中访问value
,那么你不需要它也是volatile
。但是,在某些情况下,您可以使用 double-checked locking.
public final class Lazy<T> {
private final Supplier<? extends T> initializer;
private volatile T value;
public Lazy(Supplier<? extends T> initializer) {
this.initializer = initializer;
}
public T get() {
T result = this.value;
if (result == null) {
synchronized (this) {
result = this.value;
if (result == null) {
this.value = result = this.initializer.get();
}
}
}
return result;
}
}
此代码基于 Effective Java and Java Concurrency in Practice 中显示的一些示例。请注意,此代码检查两次以查看 result
是否为 null
,一次在 synchronized
块之外,一次在内部。这样做的好处是,如果该值已经存在,则不需要同步。请注意,使用此策略,value
必须是 volatile
,因为它是在 synchronized
块之外访问的。