Java DCL情况下需要volatile关键字
我只是在实践中阅读并发性。我知道在字段的双重检查锁定机制中必须使用volatile关键字,否则线程可以读取NOTNULL对象的过时值。因为它可以在不使用volatile关键字的情况下对指令重新排序。因为对象引用可以在调用构造函数之前分配给资源变量。所以线程可以看到部分构造的对象 关于这一点,我有一个问题 我假设同步块也会限制编译器对指令的重新排序,那么为什么我们需要volatile关键字呢?Java DCL情况下需要volatile关键字,java,multithreading,concurrency,volatile,Java,Multithreading,Concurrency,Volatile,我只是在实践中阅读并发性。我知道在字段的双重检查锁定机制中必须使用volatile关键字,否则线程可以读取NOTNULL对象的过时值。因为它可以在不使用volatile关键字的情况下对指令重新排序。因为对象引用可以在调用构造函数之前分配给资源变量。所以线程可以看到部分构造的对象 关于这一点,我有一个问题 我假设同步块也会限制编译器对指令的重新排序,那么为什么我们需要volatile关键字呢? public class DoubleCheckedLocking { private stat
public class DoubleCheckedLocking {
private static volatile Resource resource;
public static Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
实际上,这里不需要使用
volatile
。使用volatile
意味着每次在线程方法中使用实例变量时,都会有多个线程,这不会优化内存的读取,而是确保它被反复读取。我故意使用volatile
的唯一一次是在有停止指示器的线程中(private volatile boolean stop=false;
)
在示例代码中创建类似的单例代码是不必要的复杂,并且不能提供任何实际的速度改进。JIT编译器非常擅长执行线程锁定优化
您最好使用以下方法创建单例:
public static synchronized Resource getInstance() {
if (resource == null) {
resource = new Resource();
}
return resource;
}
这对人类来说更容易阅读和推断其逻辑
另请参见,其中
volatile
确实通常用于线程中的某些循环结束标志。如果调用线程(T1)也从同步块(在同一锁上)读取对象,JMM仅保证线程T1将在同步块内看到由另一线程T2创建的正确初始化对象
由于T1可以将资源视为非空,因此在不经过同步块的情况下立即返回资源,因此它可以获取对象,但无法正确初始化其状态
使用volatile会带来这种保证,因为volatile字段的写入和该volatile字段的读取之间存在一种before关系。正如其他人所观察到的,在这种情况下,volatile是必要的,因为在首次访问资源时可能会发生数据竞争。如果线程
A
读取非空值,则无法保证在没有volatile的情况下,线程A
将实际访问完全初始化的资源——如果它同时是在synchronized部分中的线程B
中构建的,那么线程A还没有到达。线程A
可以尝试使用半初始化副本
仍然不建议使用volatile,while双重检查锁定,因为:
这是Initialize On Demand Holder类习惯用法,根据上面的页面
[…]的线程安全性源于以下事实:
类初始化的一部分,如静态初始化器
保证对使用该类的所有线程可见,以及
由于内部类未加载,因此延迟初始化
直到某个线程引用其某个字段或方法
@ElliottFrisch-聪明,在Java还年轻的时候就坏了(你的链接是从2001年开始的),但比更干净的替代品(见我答案中的链接)更糟糕的是,需要反复无常。有了双重检查,是的,但我坚持不需要做这样的双重检查。是的,但这无关紧要。问题是为什么在使用DCL时需要使用volatile,而不是是否应该使用DCL。正如elliot frisch在注释DCL被破坏时添加的那样,我认为最好将OP指向正确的方向。如果不使字段易失性,则DCL被破坏。你的答案是:这里没有必要使用volatile。所以你把OP和所有未来的读者都指向了错误的方向。我有一个疑问。volatile在这里做的是停止编译器对分配对象引用和构造函数执行的指令重新排序?我的意思是,在将对象引用分配给资源引用变量之前,将调用构造函数。这是真的吗?基本上是es,除了一个要点:编译器与此无关。这在运行时发生。此外,您不应该考虑volatile强制运行时做什么。您应该考虑它所提供的保证,无论这种保证在运行时是以何种方式实现的。那么,在保证其他线程时,它将看到完全初始化的对象的易变性有多大?volatile意味着写入发生在读取之前。第一个线程不可能已将分配的内存分配给引用变量。将内存分配给引用变量是写案例。然后另一个线程正在读取参考变量的值,即(读取大小写)。但是你的构造函数仍然没有被调用。@sdindiver如果你没有使用volatile,这就是可能发生的事情,而volatile(以及所有其他在关系发生之前发生的方式)保证不会发生的事情。如果有“发生之前”保证,则意味着运行时无法应用违反语言语义的重新排序(通常的顺序是:首先调用构造函数,然后将结果分配给变量)。
private static class LazyResourceHolder {
public static Resource resource = new Resource();
}
...
public static Resource getInstance() {
return LazyResourceHolder.something;
}