Java 我应该同步一个静态可变变量吗?
关于这个问题有几个问题,但大多数都回避这个问题,因为这不是问题的本意 如果我的类中有静态volatile:Java 我应该同步一个静态可变变量吗?,java,thread-safety,volatile,Java,Thread Safety,Volatile,关于这个问题有几个问题,但大多数都回避这个问题,因为这不是问题的本意 如果我的类中有静态volatile: private static volatile MyObj obj = null; 我采用以下方法: public MyObj getMyObj() { if (obj == null) { obj = new MyObj();// costly initialisation } return obj; } 我是否需要同步以确保只有一个线程写入该
private static volatile MyObj obj = null;
我采用以下方法:
public MyObj getMyObj() {
if (obj == null) {
obj = new MyObj();// costly initialisation
}
return obj;
}
我是否需要同步以确保只有一个线程写入该字段,或者任何写入都会立即对其他线程可见,以评估obj==null
conditional
换言之:volatile是否让您不必对静态变量上的写操作进行同步访问?是的,您应该绝对同步(或者使用更好的习惯用法,如)。否则,您将面临多线程多次初始化对象(然后使用不同实例)的风险 考虑这样一系列事件:
getMyObj()
并看到obj==null
getMyObj()
并看到obj==null
新的MyObj()
-我们称它为objA
新的MyObj()
-我们称它为objB
objA
分配给obj
objB
分配给obj
(此时不再是null
,因此线程A分配的对objA
的引用被覆盖)getMyObj()
并开始使用objA
getMyObj()
并开始使用objB
public class Something {
private Something() {
}
private static class LazyHolder {
public static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
这保证是安全的,因为
实例
是最终的
。Java内存模型保证在加载包含类时,final
字段被初始化,并对任意数量的线程正确可见。由于LazyHolder
是private
并且仅由getInstance()
引用,因此只有在第一次调用getInstance()
时才会加载它。此时,实例
在后台初始化,并由JVM安全发布。该代码不是线程安全的。如果多个线程执行该函数,则可以创建MyObj的多个实例。这里需要某种形式的同步
基本问题是该块代码:
if (obj == null) {
obj = new MyObj();// costly initialisation
}
它不是原子的。事实上,它离原子化还有很长的路要走。您肯定需要某种类型的锁定,以确保只有一个线程写入字段。不管波动性如何,两个线程都可以“看到”obj
为空,然后都开始用当前代码初始化
就我个人而言,我会选择三种选择之一:
- 在类加载时初始化(知道这将是延迟的,但不会像等待第一次调用
那样延迟):getMyObj
- 使用无条件锁定:
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(); }
volatile
允许一个线程对变量所做的更改被其他线程看到
设想以下执行流(假设有两个线程T1和T2):
创建两次预期只创建一次的对象。这并不是最糟糕的情况。您可能会返回一个不再分配给变量的对象。另一种处理方法是双重检查(注意:仅适用于Java 5或更高版本,有关详细信息,请参阅):
当从两个线程同时调用getInstance时,两个线程都会首先将实例视为null,另一个线程进入synchronized块并实例化该对象。另一个将看到实例不再为null,并且不会尝试实例化它。对getInstance的进一步调用将看到实例不为null,并且根本不会尝试锁定。像这样的双重检查锁定需要JDK5才能正常工作:抱歉,这里有正确的链接:@DavidHeffernan:感谢链接,我不知道“JDK5或更新版本”的要求。我将编辑答案以包含此信息。我可以将字段设置为静态,并使用静态初始值设定项或静态方法进行创建,允许同步和try/catch逻辑。这样,它的可读性更高+我不必锁定
getMyObj()
调用,因为静态初始值设定项将在类可用之前完成。@atc,您的描述不明确,因此没有代码示例,我无法评估它的线程安全性和可行性。请注意,holder习惯用法也没有使用锁(JVM在构建LazyHolder
时会有一个锁,但是getInstance()
本身是不同步的)。我不能走holder路线:它将单例强制到我的外部类上,这需要对我正在开发的整个框架进行更多的思考和更改。我需要解决的问题是静态实例的原子创建。我使用了一个静态方法,并将其声明为(obj
)static final,以允许原子分配和并发安全。@atc:不,holder路由不强制外部类为单例。现在还不清楚您在这里的意思……为了清楚起见:这个静态volatile
变量最初是在一个对象工厂中,在运行时将它注入到它负责创建的实例中。我是杜
public static MyObj getMyObj() {
return MyObjHolder.obj;
}
private static class MyObjHolder {
static final MyObj obj = new MyObj();
}
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;
}
}