当前位置: 首页 > 知识库问答 >
问题:

对公共字段的并发访问。为什么有可能观察到不一致状态?

龙德义
2023-03-14

我正在阅读B.Goetz Java并发实践,现在我正在阅读关于安全发布的3.5节。他说:

// Unsafe publication
public Holder holder;
public void initialize() {
    holder = new Holder(42);
}

这种不正确的发布可能允许另一个线程观察部分构造的对象。

我不明白为什么观察一个部分构造的子对象是可能的。假设构造函数holder(int)不允许this转义。因此,构造的引用只能由调用方观察。现在,正如JLS 17.7所述:

对引用的写入和读取始终是原子的,不管它们是实现为32位还是64位的值。

线程不可能观察到一个部分构造的对象。

我错在哪了?

共有1个答案

房学文
2023-03-14

因此,构造的引用只能由调用方观察。

这就是你的逻辑断裂的地方,尽管这似乎是一个完全合理的事情说。

首先要做的事情:17.7提到的原子性只是说,当您读取引用时,您将看到所有前一个值(以其默认值null)或所有一些后续值。您将永远不会得到一些位对应于值1和一些位对应于值2的引用,这实际上会使它成为JVM堆中一个随机位置的引用--这将是可怕的!基本上,他们说,“引用本身将是空的,或者指向内存中的一个有效位置。”但那记忆里的东西,就是事情变得怪异的地方。

我假设这个简单的持有者:

public class Holder {
    int value; // NOT final!
    public Holder(int value) { this.value = value; }
}

鉴于此,当您执行Holder=new Holder(42)时会发生什么?

  • JVM为新的Holder对象分配一些空间,它的所有字段都有默认值(即值=0)
  • JVM调用Holder构造函数
    • JVM将 .value 设置为传入值(42)。
    • 构造函数完成

    问题是另一个线程可以以任何顺序查看这些事件,因为它们之间没有同步点。这是因为构造函数没有任何特殊的同步或happens-beform语义(这是轻微的谎言,但后面会有更多的内容)。您可以在JLS17.4.4中看到“Synchronized-With”操作的完整列表;请注意,这里没有构造函数。

    因此,另一个线程可能会看到这些操作的顺序为(1,3,2)。这意味着,如果某个其他事件在事件1和事件3之间排序--例如,如果有人将holder.holder.value读入一个本地var,那么他们将看到新分配的对象,但它的值在构造函数运行之前:您将看到holder.holder.value==0。这被称为部分构造的对象,它可能非常令人困惑。

    如果构造函数有多个步骤(设置多个字段,或者设置然后更改一个字段),那么您可以看到这些步骤的任何顺序。几乎所有的赌注都落空了。哎呀!

    我在上面提到,当我断言构造函数没有任何特殊的同步语义时,我撒谎了。假设您没有泄漏this,则有一个例外:任何final字段都保证被视为构造函数末尾的字段(参见JLS 17.5)。

    您可以认为在步骤2和步骤3之间存在一种同步点,但它只适用于final字段。

    • 它不适用于非final字段
    • 它不会传递性地应用于其他同步点。
    • 但是,它会扩展到通过final字段访问的任何状态。因此,如果您有一个最终列表 ,并且您的构造函数初始化了它,然后添加了一些值,那么所有线程都可以看到该列表,它至少具有构造函数末尾的状态,包括那些add调用。(如果在没有同步的情况下修改构造函数之后的列表,则所有赌注都将再次结束。)

    这就是为什么在上面的示例value不是final的重要性。如果是,那么您将无法看到holder.holder.value==0

 类似资料:
  • 我是RxJava的新手,正在尝试从link执行多个观测值的并行执行示例:RxJava并行获取观测值 虽然上面链接中提供的示例是并行执行可观察对象,但是当我在foreach方法中添加一个Thread.sleep(TIME_IN_MILLISECONDS)时,系统开始一次执行一个可观察对象。请帮助我理解为什么Thread.sleep停止可观察对象的并行执行。 下面是导致多个观测值同步执行的修改示例:

  • 问题内容: 在大学里学习时,我不得不做一些难看的Java基础知识,例如不使用封装就可以工作,同一类中的主要方法等。(我不想在Java样式指南上展开讨论,我只是想澄清一下,我不会在大学以外写这样的东西) 我偶然发现了一种我无法向自己解释的行为: 为什么这段代码可以编译并正确运行?我怎么可能访问私有字段?由于主类位于同一类中,因此行为异常? 问题答案: 由于静态方法是类的成员,因此可以访问中的任何私有

  • 下面的代码基于@a.bertucci提供的一个示例,这里使用Android上的RxJava以固定的间隔在UI中绘制对象,其中我使用计时器压缩了一个可观察对象。当我通过调用processDelayedItems()触发订阅时,压缩的Observable中的代码[A]只执行一次,一个项目被发送到[B]。我原本希望代码[A]在触发后继续运行,并保持每1500毫秒发出一次项,但显然它在这里只运行一次。 >

  • 问题内容: 最近,我观察到在Java中访问priavte字段的意外行为。考虑以下示例,该示例说明了该行为: 为什么我可以访问类的其他对象的私有字段的内法(第二种情况)? 问题答案: 私有字段保护一个类,而不是实例。主要目的是允许一个类独立于其API实现。在它们之间隔离实例,或从相同类的静态代码中保护实例的代码都不会带来任何好处。

  • 在一个Android应用程序的开发过程中,我遇到了这个设计难题,我现在无法解决。我将感谢任何想法、变通方法或干净的解决方案:)我将尽可能地简化它: 一切都是从一个通用的观察者模式实现开始的。有一个主题在它的公共方法调用时改变状态;有观察者对这些变化作出相应的反应。在这种情况下,主题是一个有状态的主题,这意味着它有一个像自动机一样变化的内部状态控制器(一个int)。每次它的状态发生变化时,它都会像往

  • 我正在尝试从Java切换到Kotlin。但我有很多遗留代码和第三方库。我看到,Java类中经常存在没有getter和setter的公共字段,这些字段必须从其他类访问。如果没有Kotlin代码中的getter,我如何访问Java类的公共字段?