简体   繁体   English

从未分配null的volatile变量可以包含null吗?

[英]Can a volatile variable that is never assigned null to ever contain null?

Can in the following conceptual Java example: 可以在下面的概念性Java示例中:

public class X implements Runnable {
    public volatile Object x = new Object();

    @Runnable
    public void run() {
        for (;;) {
            Thread.sleep(1000);
            x = new Object();
        }
    }
}

x ever be read as null from another thread? x是否曾经从另一个线程读取为null

Bonus: Do I need to declare it volatile (I do not really care about that value, it suffices that sometime in the future it will be the newly assigned value and never is null) 奖励:我是否需要声明它为volatile(我不太在乎那个值,它足以满足将来某个时候它将是新分配的值并且永远不会为null的问题)

Technically, yes it can. 从技术上讲,是的。 That is the main reason for the original ConcurrentHashMap's readUnderLock . 这是原始ConcurrentHashMap的readUnderLock的主要原因 The javadoc even explains how: Javadoc甚至说明了如何:

Reads value field of an entry under lock. 读取处于锁定状态的条目的值字段。 Called if value field ever appears to be null. 如果value字段曾经为空,则调用此方法。 This is possible only if a compiler happens to reorder a HashEntry initialization with its table assignment, which is legal under memory model but is not known to ever occur. 仅当编译器碰巧使用表分配对HashEntry初始化进行重新排序时才有可能,这在内存模型下是合法的,但从未发生过。

Since the HashEntry's value is volatile this type of reordering is legal on consturction. 由于HashEntry的value是可变的,因此这种重新排序在构造上是合法的。

Moral of the story is that all non-final initializations can race with object construction. 这个故事的寓意是,所有非最终的初始化都可以与对象构造竞争。


Edit: @Nathan Hughes asked a valid question: 编辑:@Nathan Hughes提出了一个有效的问题:

@John: in the OP's example wouldn't the construction have happened before the thread the runnable is passed into started? @John:在OP的示例中,在将可运行线程传递给启动之前,构造是否会发生? it would seem like that would impose a happens-before barrier subsequent to the field's initialization. 似乎这会在字段初始化之后强行在障碍之前发生。

Doug Lea had a couple comments on this topic, the entire thread can be read here . 道格·利阿(Doug Lea)就这个话题发表了一些评论,整个话题可以在这里阅读 He answered the comment: 他回答了这个评论:

But the issue is whether assignment of the new C instance to some other memory must occur after the volatile stores. 但是问题是在易失性存储之后是否必须将新的C实例分配给其他内存。

With the answer 有了答案

Sorry for mis-remembering why I had treated this issue as basically settled: Unless a JVM always pre-zeros memory (which usually not a good option), then even if not explicitly initialized, volatile fields must be zeroed in the constructor body, with a release fence before publication. 抱歉,我为什么将这个问题视为基本解决:除非JVM始终将内存预先清零(通常这不是一个好选择),否则即使未显式初始化,也必须在构造函数主体中将volatile字段清零。发布之前的发布围栏。 And so even though there are cases in which the JMM does not strictly require mechanics preventing publication reordering in constructors of classes with volatile fields, the only good implementation choices for JVMs are either to use non-volatile writes with a trailing release fence, or to perform each volatile write with full fencing. 因此,即使在某些情况下,JMM并没有严格要求机制来防止具有可变字段的类的构造函数中的发布重新排序,对于JVM来说,唯一好的实现选择是要么使用带有尾随释放栅栏的非易失性写入,要么执行具有完整防护的每个易失性写操作。 Either way, there is no reordering with publication. 无论哪种方式,都不会对发布进行重新排序。 Unfortunately, programmers cannot rely on a spec to guarantee it, at least until the JMM is revised. 不幸的是,程序员至少在修订JMM之前就不能依靠规范来保证它。

And finished with: 并完成:

  • Programmers do not expect that even though final fields are specifically publication-safe, volatile fields are not always so. 程序员并不期望即使最终字段是专门用于发布安全的,但可变字段也不总是如此。

  • For various implementation reasons, JVMs arrange that volatile fields are publication safe anyway, at least in cases we know about. 由于各种实现原因,至少在我们所知的情况下,JVM安排易失字段无论如何都是发布安全的。

  • Actually updating the JMM/JLS to mandate this is not easy (no small tweak that I know applies). 实际上,更新JMM / JLS以强制执行此操作并不容易(我知道可以进行一些细微的调整)。 But now is a good time to be considering a full revision for JDK9. 但是现在是考虑对JDK9进行完整修订的好时机。

  • In the mean time, it would make sense to further test and validate JVMs as meeting this likely future spec. 同时,进一步测试和验证JVM是否符合此可能的未来规格将是有意义的。

This depends on how the X instance is published. 这取决于X实例的发布方式。

Suppose x is published unsafely, eg. 假设x发布不安全,例如 through a non- volatile field 通过非易失volatile领域

private X instance;
...
void someMethod() {
    instance = new X();
}

Another thread accessing the instance field is allowed to see a reference value referring to an uninitialized X object (ie. where its constructor hasn't run yet). 允许另一个访问instance字段的线程看到引用未初始化X对象的参考值(即,其构造函数尚未运行的地方)。 In such a case, its field x would have a value of null . 在这种情况下,其字段x的值为null

The above example translates to 上面的例子翻译成

temporaryReferenceOnStack = new memory for X // a reference to the instance
temporaryReferenceOnStack.<init> // call constructor
instance = temporaryReferenceOnStack;

But the language allows the following reordering 但是语言允许以下重新排序

temporaryReferenceOnStack = new memory for X // a reference to the instance
instance = temporaryReferenceOnStack;
temporaryReferenceOnStack.<init> // call constructor

or directly 或直接

instance = new memory for X // a reference to the instance
instance.<init> // call constructor

In such a case, a thread is allowed to see the value of instance before the constructor is invoked to initialize the referenced object. 在这种情况下,在调用构造函数初始化引用的对象之前,允许线程查看instance的值。

Now, how likely this is to happen in current JVMs? 现在,这在当前的JVM中发生的可能性有多大? Eh, I couldn't come up with an MCVE. 嗯,我无法提出MCVE。


Bonus: Do I need to declare it volatile (I do not really care about that value, it suffices that sometime in the future it will be the newly assigned value and never is null) 奖励:我是否需要声明它为volatile(我不太在乎那个值,它足以满足将来某个时候它将是新分配的值并且永远不会为null的问题)

Publish the enclosing object safely. 安全发布封闭对象。 Or use a final AtomicReference field which you set . 或使用您setfinal AtomicReference字段。

No. The Java memory model guarantees that you will never seen x as null. 否。Java内存模型保证您永远不会将x视为null。 x must always be the initial value it was assigned, or some subsequent value. x必须始终是为其分配的初始值或某个后续值。

This actually works with any variable, not just volatile . 实际上,这适用于任何变量,而不仅仅是volatile What you are asking about is called "out of thin air values". 您要问的是所谓的“凭空赚钱”。 Cf Java Concurrency in Practice which talks about this concept in some length. Cf Java Concurrency in Practice详细讨论了此概念。

The other part of your question "Do I need to declare x as volatile:" given the context, yes, it should be either volatile or final. 给定上下文,您问题的另一部分“我是否需要将x声明为volatile:”是的,它应该是volatilefinal. Either one provides safe publication for your object referenced by x . 任一种都可以为x引用的对象提供安全的发布。 Cf Safe Publication. cf 安全出版物。 Obviously, x can't be changed later if it's final . 显然,如果xfinal以后不能更改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM