关于这个问题有几个问题,但是大多数都围绕着这个问题,因为这不是问题的意图。
如果我班上有静态不稳定因素:
private static volatile MyObj obj = null;
在下面的方法中我做了:
public MyObj getMyObj() {
if (obj == null) {
obj = new MyObj();// costly initialisation
}
return obj;
}
我需要同步以确保只有一个线程写入字段,或者任何写入是否会立即显示给评估obj == null
条件的其他线程?
换句话说:volatile是否会让你不得不同步访问静态变量上的写入?
答案 0 :(得分:8)
您肯定需要一些类型的锁定,以确保只有一个线程写入该字段。无论波动性如何,两个线程都可以“看到”obj
为空,然后两者都开始使用当前代码进行初始化。
我个人会选择以下三个选项之一:
在类加载时初始化(知道这将是懒惰的,但不像在第一次调用getMyObj
时那样懒惰):
private static final MyObj obj = new MyObj();
使用无条件锁定:
private static MyObj obj;
private static final Object objLock = new Object();
public static MyObj getMyObj() {
synchronized(objLock) {
if (obj == null) {
obj = new MyObj();
}
return obj;
}
}
以这种方式使用嵌套类来实现懒惰:
public static MyObj getMyObj() {
return MyObjHolder.obj;
}
private static class MyObjHolder {
static final MyObj obj = new MyObj();
}
答案 1 :(得分:3)
是的,你应该绝对同步(或使用像Singleton Holder idiom这样的更好的习语)。否则,您将面临多个线程多次初始化对象的风险(然后使用不同的实例)。
考虑一系列这样的事件:
getMyObj()
并看到obj == null
getMyObj()
并看到obj == null
new MyObj()
- 让我们称之为objA
new MyObj()
- 让我们称之为objB
objA
分配给obj
objB
分配给obj
(此时此时不再是null
,因此会覆盖由线程A分配的objA
的引用<) / LI>
getMyObj()
并开始使用objA
getMyObj()
并开始使用objB
这种情况可能发生在任意数量的线程中。请注意,尽管在这里,为了简单起见,我假设事件的严格排序,在真实的多线程环境中,事件1-2,3-4和/或7-8可以在时间上部分或完全重叠,而不会改变结束结果
持有人习语的一个例子:
public class Something {
private Something() {
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
这保证是安全的,因为INSTANCE
是final
。 Java内存模型保证在加载包含类时,final
字段被初始化并且可以正确地显示给任意数量的线程。由于LazyHolder
为private
且仅由getInstance()
引用,因此只有在首次调用getInstance()
时才会加载它。此时,INSTANCE
在后台初始化并由JVM安全发布。
答案 2 :(得分:1)
不,您仍需要同步访问权限。 volatile
允许其他线程看到一个线程对变量所做的更改。
想象一下下面的执行流程(假设有两个线程T1和T2):
您创建两次您希望仅创建一次的对象。这不是更糟糕的情况。您最终可能会返回一个不再分配给您的变量的对象。
答案 3 :(得分:0)
该代码不是线程安全的。如果多个线程执行该函数,则可以创建MyObj的多个实例。你需要某种形式的同步。
根本问题是这个区块代码:
if (obj == null) {
obj = new MyObj();// costly initialisation
}
不是原子的。事实上,它距离原子化还有很长的路要走。
答案 4 :(得分:0)
另一种处理此问题的方法是仔细检查(注意:仅适用于Java 5或更高版本,有关详细信息,请参阅here):
public class DoubleCheckingSingletonExample
{
private static final Object lockObj = new Object();
private static volatile DoubleCheckingSingletonExample instance;
private DoubleCheckingSingletonExample()
{
//Initialization
}
public static DoubleCheckingSingletonExample getInstance()
{
if(instance == null)
{
synchronized(lockObj)
{
if(instance == null)
{
instance = new DoubleCheckingSingletonExample();
}
}
}
return instance;
}
}
当同时从两个线程调用getInstance时,两者都将首先看到实例为null,另一个进入synchronized-block,并实例化该对象。另一个将看到实例不再为null,并且不会尝试实例化它。对getInstance的进一步调用将看到该实例不为null,并且根本不会尝试锁定。