我正在寻找类似于Is it appropriate to use AtomicReference.compareAndSet to set a reference to the results of a database call?但有不同要求的问题的答案。
目标是仅创建一次ObjectWithSideEffectConstructor
实例,以避免重复的副作用。构造必须在setUp()
中进行。多个线程将调用setUp()
。类似地,将有一个tearDown()
用于从对象中回收资源,这里省略了。问题:实现目标的最佳做法是什么?
仅仅使用AtomicReference
是不够的,因为构造函数将首先被执行,因此副作用。
private static AtomicReference<ObjectWithSideEffectConstructor> ref =
new AtomicReference<ObjectWithSideEffectConstructor>()
void setUp() {
ref.compareAndSet(null, new ObjectWithSideEffectConstructor());
}
使用Is it appropriate to use AtomicReference.compareAndSet to set a reference to the results of a database call?的答案无效,因为volatile
缺乏同步。将有多个线程进入if
的窗口。
private static volatile ObjectWithSideEffectConstructor obj;
void setUp() {
if (obj == null) obj = new ObjectWithSideEffectConstructor();
}
简单修复就是
private static ObjectWithSideEffectConstructor obj;
private static final Object monitor = new Object();
void setUp() {
synchronized (monitor) {
if (obj == null) obj = new ObjectWithSideEffectConstructor();
}
}
类似地,具有易失性监视器的DCL可以提供更好的读取性能。但两者都需要一定程度的同步,因此预计性能会更差。
我们也可以使用FutureTask
。它更有效,因为一旦创建了对象,后续的FutureTask.get()
将无阻塞地返回。但它肯定比synchronized
复杂得多。
private static final AtomicReference<FutureTask<ObjectWithSideEffectConstructor>> ref =
new AtomicReference<FutureTask<ObjectWithSideEffectConstructor>>();
void setUp() {
final FutureTask<ObjectWithSideEffectConstructor> future =
new FutureTask<ObjectWithSideEffectConstructor>(
new Callable<ObjectWithSideEffectConstructor>() {
@Override
public ObjectWithSideEffectConstructor call() throws InterruptedException {
return new ObjectWithSideEffectConstructor();
}
}
);
if (ref.compareAndSet(null, future)) future.run();
ref.get().get();
}
感谢您的建议。
答案 0 :(得分:3)
如果您正在谈论threadsafe的lazy initialization singleton,这里有一个很酷的代码模式,可以完成 100%线程安全延迟初始化而无需任何同步代码< /强>:
public class MySingleton {
private static class MyWrapper {
static MySingleton INSTANCE = new MySingleton();
}
private MySingleton () {}
public static MySingleton getInstance() {
return MyWrapper.INSTANCE;
}
}
这种编码模式称为Initialization-on-demand holder idiom。它只会在调用getInstance()
时实例化单例,并且它是100%线程安全的!这是经典之作。
它的工作原理是因为类加载器有自己的同步来处理类的静态初始化:保证在使用类之前所有静态初始化都已完成,并且在此代码中,类仅在{{1}内使用方法,所以当加载类加载内部类时是。
答案 1 :(得分:1)
使用Enum在Java 5或更高版本中实现Singleton:
Enum是线程安全的,通过Enum实现Singleton可确保您的单例即使在多线程环境中也只有一个实例。 让我们看一个简单的实现:
public enum SingletonEnum
{
INSTANCE;
public void doStuff()
{
System.out.println("Singleton using Enum");
}
}
// How to use in other classes
public static void main(String[] args)
{
SingletonEnum.INSTANCE.doStuff();
}
答案 2 :(得分:1)
始终对单例使用枚举类型,它不仅优雅地强制执行单例,还可以防止常见的编程错误,例如当单例从其超类继承clone()方法并且程序员忘记使用私有方法覆盖它时宣言。或者当您忘记覆盖deserialisable,并允许程序员序列化您的单例,声明一个新实例,然后反序列化旧实例。
或者,如果使用静态工厂模式,则可以声明实例字段瞬态并使用readresolve方法。如果你可以在设计过程的后期改变你的想法,那么这就提供了灵活性。
信用:基于有效Java的答案,J Bloch(第3项),每本Java程序员都应该阅读,拥有和定期参考的书......
答案 3 :(得分:0)
我假设你只想要一个 ObjectWithSideEffectConstructor。这里有一个问题,1)它是否会发生两次你想要避免的副作用,或者2)你只需要一致(单例)引用。
无论哪种方式,synchronized
都是一个很好的标准选项。它会使构建的其他线程保持第二个实例,而第一个线程处于安装状态。
如果你处于情况1),可能需要使用synchronized。如果启动后 的性能非常重要,您可以考虑使用AtomicReference.get()
快速路径在之前同步部分,以便在启动后避免同步部分完成了。
如果你处于情境2),那么 - 你的问题并不是很清楚 - 有施工的副作用,但你不关心重复这个 - 只要客户代码只“看到”一致的单一引用。
在第二种情况下,可以使用AtomicReference.get()
来检查它是否已经初始化,如果是,则返回。然后,线程将进入“竞争部分”,在那里他们将构造(可能是多个)ObjectWithSideEffectConstructor。最后,会有一个compareAndSet
,这样只有一个线程设置单例..失败的线程会回落到AtomicReference.get()
以获取正确的单例。
Performancewise,对AtomicReference
的单个调用比synchronized
块快 - 但我不确定是否通过双重和三重检查&amp;构建不需要的副作用物体,第二种方法是。一个简单的synchronized
块也可能更简单&amp;更快。
我有兴趣看一些测量结果。
答案 4 :(得分:0)
同步方法将是最佳选择。如果您确实需要性能,则需要重构代码以进行单线程预初始化。使用任何其他形式都会导致副作用,如singleton pattern。
中所述答案 5 :(得分:0)
对于它的价值,FutureTask方法实际上并不需要所有代码;不需要AtomicReference
,也不应该同时调用 run()
和 get()
。所以你可以稍微简化一下:
private static final Future<ObjectWithSideEffectConstructor> future =
new FutureTask<>(
new Callable<ObjectWithSideEffectConstructor>() {
@Override
public ObjectWithSideEffectConstructor call() throws InterruptedException {
return new ObjectWithSideEffectConstructor();
}
}
);
void setUp() {
future.run(); // or future.get(), if you want to get any exception immediately
}
此外,使用Java 8,可以更简单地编写初始化表达式;以上可以简化为:
private static final Future<ObjectWithSideEffectConstructor> future =
new FutureTask<>(ObjectWithSideEffectConstructor::new);
void setUp() {
future.run(); // or future.get(), if you want to get any exception immediately
}