当前位置: 首页 > 面试题库 >

易失性担保和无序执行

蔚桐
2023-03-14
问题内容

重要的编辑* 我知道发生 两个分配的线程中 的“发生在前面” 我的问题是,当“ a”仍然为空时, 另一个 线程是否有可能读取“
b”为非空。因此,我知道,如果您从与先前调用 setBothNonNull(…)的 线程相同的线程中调用 doIt()
,则它不会引发NullPointerException。但是,如果一个调用 doIt方法() 从另一个线程 不同于调用
setBothNonNull(…)

*__

请注意,这仅仅是对volatile关键字和volatile保证:这是 不是
synchronized关键字(所以请不要回答“您必须使用同步”因为我没有任何问题要解决:我只是想了解volatile担保(或缺乏保证)关于乱序执行)。

假设我们有一个包含两个volatileString引用的对象,该对象被构造函数初始化为null,并且只有一种方法可以修改这两个String:通过调用
setBoth(…) ,之后我们只能将它们的引用设置为non-空引用(仅允许构造函数将其设置为空)。

例如(这只是一个例子,没有问题):

public class SO {

    private volatile String a;
    private volatile String b;

    public SO() {
        a = null;
        b = null;
    }

    public void setBothNonNull( @NotNull final String one, @NotNull final String two ) {
        a = one;
        b = two;
    }

    public String getA() {
        return a;
    }

    public String getB() {
        return b;
    }

}

setBothNoNull(…)中 ,分配非空参数“ a”的行出现在分配非空参数“ b”的行之前。

然后,如果我这样做(再次,没有问题,接下来是问题):

doIt() {
    if ( so.getB() != null ) {
        System.out.println( so.getA().length );
    }
}

我的理解是否正确,由于执行 混乱, 我可以得到 NullPointerException 吗?

换句话说:不能保证因为我读了一个非空的“ b”,所以我会读一个非空的“ a”?

因为由于乱序(多)处理器和volatile工作方式,可以在“ a”之前分配“ b”?

volatile保证写入后的读取将始终看到最后写入的值,但是这里存在乱序的“问题”对吗?(再次,“问题”是故意创建的,以试图理解volatile关键字和Java内存模型的语义,而不是解决问题)。


问题答案:

不,您永远不会获得NPE。这是因为volatile也具有引入事前发生关系的记忆效果。换句话说,它将防止重新排序

a = one;
b = two;

上面的语句,会不会重新排序,并将所有线程将观察值onea,如果b已经有了价值two

这是David
Holmes在其中进行解释的线程: http
:
//markmail.org/message/j7omtqqh6ypwshfv#query
:+
page
:1+
mid
:
34dnnukruu23ywzy+state
results

编辑(对后续措施的回应):福尔摩斯说的是,如果只有线程A ,则编译器 理论上
可以进行重新排序。但是,还有其他线程,它们可以检测到重新排序。这就是为什么不允许编译器进行重新排序的原因。Java内存模型要求编译器专门确保没有线程能够检测到这种重新排序。

但是,如果有人从另一个线程而不是setBothNonNull(…)调用doIt()呢?

不,您永远不会拥有NPE。volatile语义确实强加了线程间排序。这意味着,对于所有现有线程,的分配one发生在的分配之前two



 类似资料:
  • 问题内容: 从实践中的Java并发性书中: 为了安全地发布对象,必须同时使对该对象的引用和该对象的状态对其他线程可见。可以通过以下方式安全地发布正确构造的对象: 从静态初始化程序初始化对象引用 将对它的引用存储到volatile字段或AtomicReference中 将对其的引用存储到正确构造的对象的最终字段中 将对它的引用存储到由 锁适当保护的字段中。 我的问题是: 项目符号点2和3之间有什么区

  • 在阅读了这个问题和这个(尤其是第二个答案)之后,我对volatile及其关于记忆障碍的语义感到非常困惑。 在上面的例子中,我们写入一个易失性变量,这会导致一个mitch,这反过来会将所有挂起的存储缓冲区/加载缓冲区刷新到主缓存,使其他缓存行无效。 然而,非易失性字段可以优化并存储在寄存器中,例如?那么,我们如何才能确保给定一个写入易失性变量之前的所有状态变化都是可见的呢?如果我们有1000件东西呢

  • 我对下面的代码段有一个问题。结果可能有一个结果[0,1,0](这是用JCStress执行的测试)。那么这是怎么发生的呢?我认为数据写入(data=1)应该在Actor2(guard2=1)中写入到guard2之前执行。我说得对吗?我问,因为很多时候我读到挥发物周围的说明没有重新排序。此外,根据这一点:http://tutorials.jenkov.com/java-concurrency/vola

  • 我已经阅读了许多相互矛盾的信息(msdn,SO等),关于易失性和VoletleRead(ReadAcquireFence)。 我理解这些限制的内存访问重新排序含义——我仍然完全搞不清楚的是新鲜度保证——这对我来说非常重要。 msdn doc用于挥发性提及: (…)这样可以确保字段中始终存在最新的值。 挥发性字段的msdn文档提到: 对易失性字段的读取称为易失性读取。易失性读取具有“获取语义”;也就

  • 我对易变语义几乎没有疑问 假设有三个线程T1、T2和T3,以及给定类的一个实例。 假设发生以下读/写操作序列: 我知道可以保证点9的T1将看到点7的值,点10的T1将看到点6的值(确切地说,至少和这个值一样最新)。 但是,这些说法是真的吗? Java内存模型保证,点11处的T1将看到至少与点5处的T3相同的最新值(来自T3或更实际的本地内存的值,但即使共享内存中有更实际的值,T1也可能看不到) 请

  • a)为什么除了“00”之外还有其他输出? b)如何修改代码以便始终打印“00”。 对于a)我的回答是:在没有任何volatile/同步构造的情况下,编译器可以重新排序一些指令。特别是“this.initialint=val;”和“this.flag=true;”可以切换,这样就可以发生这种情况:线程都启动了,t1充电在前面。给定重新排序的指令,它首先设置flag=true。在它到达“this.in