【问题标题】:Unsafe publication concurrency java [duplicate]不安全的发布并发java [重复]
【发布时间】:2013-09-04 15:55:00
【问题描述】:

Java并发实战书给出了不安全发布的例子

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.");
    }
}

上面的代码似乎是线程安全的。如果 n 是公共变量,它将不是线程安全的。书的例子错了吗?

【问题讨论】:

  • 只要 n 是私有的,任何东西都不能修改,所以你当前的代码确实是线程安全的。
  • 不考虑反射。
  • 基本上,这曾经是一个严重的潜在问题,但 Java 5 规范使内存模型更加严格,不再是问题。 (n 应该仍然是 final。)
  • @zhong.j.yu 人们一直这么说。我想看一个例子。我还没有看到一个。他们都假设你已经有一个参考,但没有说明你是怎么得到它的。
  • @Cruncher VM 可以分配一个空白对象,将其地址分配给一个共享变量(其他线程可以提前观察到),然后调用构造函数。

标签: java multithreading concurrency


【解决方案1】:

安全发布是关于内存可见性。内存可见性的概念比竞争条件等其他线程安全问题要复杂一些。

当一个线程以特定顺序执行的操作似乎以不同的顺序为另一个线程执行时,就会出现内存可见性问题(这可能是由编译器或 CPU 进行的优化引起的)。

在你的情况下:

// Thread A
h = new Holder(42);

// Thread B
h.assertSanity();

对于线程 A,n 肯定在 h 之前初始化。

但是在没有安全发布的情况下,它不能保证与线程 B 相同。线程 B 可能会看到 h 处于初始化状态,但 n 还不会被初始化。此外,线程 B 观察到的n 的状态可能会在评估n != n 期间发生变化,从而导致assertSanity() 抛出异常。

请注意,此问题并非在所有情况下都会发生。您可能永远不会看到这种情况发生,但 Java 内存模型在这种情况下仍不能保证正确性。

【讨论】:

  • +1 很好的解释。有人知道为什么不更改 Java 规范来解决这个问题吗?基本上,使构造函数之前自动发生。那会破坏什么?
  • @user949300 要求在构造函数完成之前不分配对象引用将不利于优化。
  • 我很确定(需要查一下)在构造函数中进行的分配总是对其他线程可见。所以n 在从线程 B 观察时总是会被初始化
  • @user949300:内存模型的重点是任何额外的保证都不允许某些优化。据我所知,在具有宽松内存模型的 CPU 上,可能需要在每个构造函数中插入内存屏障,这可能会严重损害性能。
  • +1 为此,您必须在不触及任何读/写障碍的情况下传递对象。即,如果您使用线程安全集合或 volatile 字段,则不会发生这种情况。
【解决方案2】:

它不是线程安全的,因为在创建类的对象时Holder.n 被分配了一个默认值0

因此,一些线程可以看到传递给构造函数的值0,而其他线程可以看到值n

【讨论】:

  • 除非在构造函数返回之前没有任何东西可以获取对象的引用。构造函数不会将自身的引用分配给构造函数中的任何内容。它是线程安全的。
  • @Cruncher 现在是这样。 Java 5 之前的版本不是真的。
【解决方案3】:

这门课很好。我们通常应该期望对象仅通过安全发布在线程之间共享。在这种情况下,Holder.n 对于任何观察者来说都是相同的常量值。

通过不安全的发布共享对象有时是合理的,但是程序员必须负责确保该对象可以在这种情况下使用,例如String

大多数类不需要也不应该被设计为使用不安全的发布。

【讨论】:

  • 您能否详细说明何时需要不安全的出版物?
  • 嗯...我不会。但我不能排除在一些狭隘的场景下这样做是件好事的可能性。
  • 你应该改写“这个类很好只要它被安全发布” - 我知道这是你的意思,但不是很清楚。
  • @assylias 我的观点是可以假设安全发布。默认情况下,我们不需要担心不安全的发布。旨在处理不安全发布的类,例如字符串,一定有其特殊的原因。 “不可变类”并不像它所宣传的那么重要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-10-08
  • 2020-08-30
  • 2013-11-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多