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

Brian Goetz的不当出版

卫宏硕
2023-03-14

这个问题之前已经发布过,但没有提供有效的实例。因此,Brian提到,在某些情况下,AssertionError可能出现在以下代码中:

public class Holder {
  private int n;
  public Holder(int n) { this.n = n; }

  public void assertSanity() {
    if (n!=n)
      throw new AssertionError("This statement is false");
  }
}

当持有者像这样被不恰当地发布时:

class someClass {
    public Holder holder;

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

我理解,当对holder的引用在对象holder的实例变量对另一个线程可见之前可见时,就会发生这种情况。因此,我制作了以下示例来激发这种行为,从而引发以下类的AssertionError:

public class Publish {

  public Holder holder;

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

  public static void main(String[] args) {
    Publish publish = new Publish();
    Thread t1 = new Thread(new Runnable() {
      public void run() {
        for(int i = 0; i < Integer.MAX_VALUE; i++) {
          publish.initialize();
        }
        System.out.println("initialize thread finished");
      }
    });

    Thread t2 = new Thread(new Runnable() {
      public void run() {
        int nullPointerHits = 0;
        int assertionErrors = 0;
        while(t1.isAlive()) {
          try {
            publish.holder.assertSanity();
          } catch(NullPointerException exc) {
            nullPointerHits++;
          } catch(AssertionError err) {
            assertionErrors ++;
          }
        }
        System.out.println("Nullpointerhits: " + nullPointerHits);
        System.out.println("Assertion errors: " + assertionErrors);
      }
    });

    t1.start();
    t2.start();
  }

}

无论我运行代码多少次,断言错误都不会发生。因此,对我来说,有几种选择:

  • jvm实现(在我的例子中是Oracle的1.8.0.20)强制在html" target="_blank">构建对象期间设置的不变量对所有线程都是可见的。
  • 这本书是错的,我怀疑作者是Brian Goetz... nuf说
  • 我在上面的代码中做错了什么

所以我的问题是:-有人成功地触发过这种AssertionError吗?那么用什么代码呢?-为什么我的代码没有触发AssertionError?

共有2个答案

公孙鸿才
2023-03-14

您正在寻找一个非常罕见的条件。即使代码读取了未初始化的n,它也可能在下次读取时读取相同的默认值,因此您正在寻找的竞争需要在这两个相邻读取之间进行更新。

问题是,每个优化器一旦开始处理代码,就会将代码中的两个读取强制为一个,因此在此之后,即使单个读取的计算结果为默认值,也永远不会得到断言错误。

此外,由于访问发布。holder不同步,优化器可以准确读取其值一次,并假设在所有后续迭代中保持不变。因此,优化后的第二个线程将始终处理同一个对象,该对象永远不会返回到未初始化状态。更糟糕的是,乐观优化器可能会假设n始终是42,因为您在此运行时从未将其初始化为其他内容,并且它不会考虑您想要竞争条件的情况。因此,这两个环路都可以优化为无操作。

换句话说:如果您的代码在第一次访问时没有失败,那么在后续迭代中发现错误的可能性会显著下降,可能降到零。这与您的想法相反,即让代码在长循环中运行,希望您最终会遇到错误。

获得数据竞争的最佳机会是代码的第一次非优化解释执行。但是请记住,即使在纯解释模式下运行整个测试代码,特定数据竞争的机会仍然非常低。

慕高格
2023-03-14

您的程序没有正确同步,因为该术语由Java内存模型定义。

然而,这并不意味着任何特定的运行都会显示您正在寻找的断言失败,也不意味着您一定会看到该失败。可能是您的特定VM碰巧以一种永远不会暴露同步失败的方式处理该特定程序。或者可能会出现虽然容易失败,但可能性很小的情况。

不,您的测试没有为编写无法以这种特定方式正确同步的代码提供任何理由。你不能从这些观察中得出结论。

 类似资料:
  • 对于NetBeans 8 beta中基于Maven 3的项目,当我在上下文中单击“项目”导航器的“依赖项”项并选择“添加依赖项…”菜单项时,会出现一个用于搜索存储库的对话框。 每次我输入“slf4j api”并选择找到的项目“org.slf4j:self4j api”,我都会得到不同的结果。有时在版本列表中我会看到1.7.0,有时是1.7.5,但从来没有最新版本:1.7.6可从slf4j下载页面下

  • 问题内容: 这是来自JCiP的示例。 在第34页上: [15]这里的问题不是Holder类本身,而是Holder没有正确发布。但是,可以通过将n字段声明为final来使Holder免受不适当发布的影响,这将使Holder不变。 从这个答案: final的规范(请参阅@andersoj的答案)保证当构造函数返回时,将对final字段进行适当的初始化(从所有线程可见)。 从维基: 例如,在Java中,

  • 我下载了Geotrellis,并试图按照下面的说明进行操作:https://github.com/locationtech/Geotrellis 我将解析器和libraryDependency拉入build.sbt文件。 已安装:安装了Spark2.1.1、Scala2.11.12、Java8、JRE和JDK。 将Spark降级到2.1.1版本(Spark的引用版本),以前使用2.3.3和2.4.

  • 我有一个Spring/JPA配置,其中Hibernate作为持久性提供者。但是,我不明白为什么在没有打开事务的情况下对以下DAO代码调用save()时没有抛出TransactionRequiredException(DAO/服务中没有@Transactional): 正如预期的那样,实体没有保存,但为什么没有引发异常?持久化的javadoc表示,持久化()应该抛出一个“Transaction必需的

  • 问题内容: 我有这样的方法: 我想抛出一个内。编译器不允许我这样做,因为不允许将我的方法扔在那里。但是我需要抛出一个的子类来进行测试 (我不能抛出Unchecked)。显然这是一个hack,但我需要进行测试。我尝试过EasyMock,但它也不允许我这样做。任何想法如何做到这一点? 谢谢,肖恩·阮 问题答案: 方法1: Alexey Ragozin的这篇文章介绍了如何使用泛型技巧引发未声明的检查异常