【问题标题】:safe publication of mutable object可变对象的安全发布
【发布时间】:2018-05-24 09:09:53
【问题描述】:

我阅读了几个相关问题,但没有一个解释安全发布持有人的方法。我仍然对 Java Concurrency in Practice 中的示例感到困惑,第 3.5 节:

有类Holder:

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

及其不安全的发布:

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

AssertionError 可能会被抛出,我同意。作者写道,这是因为出版不安全,但另一方面没有答案: 正确的发布方式是什么? 他们指出了 4 个安全的发布习语,但我不明白,为什么它们会在上述情况下起作用:

为了安全地发布对象,对对象的引用和 对象的状态必须同时对其他线程可见。 正确构造的对象可以通过以下方式安全发布:

  1. 从静态初始化器初始化对象引用;
  2. 将对其的引用存储到易失性字段或 AtomicReference 中;
  3. 将对它的引用存储到正确构造的对象的最终字段中;
  4. 或将对其的引用存储到由以下人员正确保护的字段中 一把锁。

我同意 1&4,但怀疑为什么以下出版物会起作用:

//safe publication
public volatile Holder holder;

//safe publication
public final Holder holder;

volatile & final 只对引用有影响,对被引用的对象状态没有影响,所以我认为 AssertionError 仍然可能,对吧?

作者展示了如何使持有者免受不安全出版物的影响,而不是对出版物进行改进,方法是:

private final int n;

我很好奇以下是否也可以使用?它与(有效)不变性有何联系?

private volatile int n;

这是我的第一个问题,谢谢你的帮助!

【问题讨论】:

    标签: java multithreading immutability


    【解决方案1】:

    其实我觉得volatile在这里解释最简单。 Un安全发布发生在操作可以被重新排序并且 volatile 防止这种情况发生时。我可以解释更多,但它是already explained far more accurate than I will do

    正如here 所解释的那样,基本上下面会插入适当的内存屏障,以防止重新排序。本质上,volatile 的作用是,如果 ThreadA 读取 ThreadB 执行的 volatile 更新,则保证还可以看到在该 volatile 写入之前 完成的所有更新。

    final 也使事情变得安全,特别是 written in the JLS

    但是这里有两种情况:将对它的引用存储到正确构造的对象的最终字段中

    所以根据JLS,这是安全的发布:

    class Holder {
        private final int n; // making final here
    }
    

    顺便说一句,插入了适当的内存屏障,防止构造函数中的存储在发布引用本身时重新排序。

    这个例子怎么样?

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

    还有其他地方:

     class Other {
        final Holder holder;
        Other(){
            holder = new Holder();
            holder.setN(12);
        }
     }
    

    根据this看来,这仍然是安全发布

    【讨论】:

    • good tip! 好的,假设final Holder holderfinal 行为被“传播”(用于冻结目的)到其内容:private int n。它实际上是我的一个问题的答案。谢谢!我仍然对volatile Holder holder should not work AFAIU 的解决方案感到困惑。
    • @88mariusz 我稍后会读到,但是这个shipilev.net/blog/2014/safe-public-construction(我更相信Shipilev)说这对于安全发布来说非常好......我会在那个链接上阅读以后有空的时候
    • @88mariusz 有时间,再读一遍。这真的是指不同的东西。仔细查看第一个示例,当然没有安全写入,因为没有写入其他线程观察到的 volatile 变量,这就是发生在之前的情况。实际上你可以在这里阅读一个很好的答案:stackoverflow.com/a/48339266/1059372
    • 带有基本 FinalWrapper 的示例 因为现在写到构造函数之外的 final 字段已经太晚了来自Shipilev 对我来说太多了。它使previously good tip 不适用于我的问题......我放弃了。无论如何,谢谢@Eugene
    【解决方案2】:

    使整数易失性并将其与您正在并发的线程同步到锁定对象。

    这不是确切的代码,但更多的是让您了解的想法。没有两件事可以同时作用于一件事。这就是导致程序甚至操作系统死锁的原因。

    第一类:

    public static final Object lock = new Object();
    private Holder holder;
    public abstract void method1(); //Assume these two go to different places
    public abstract void method2(); //At different times w/ different implementations
    

    线程1:

    public void method1() {
        synchronized(Class1.lock) {
            holder.assertMadness();
        }
    }
    

    线程2:

    public void method2() {
        synchronized(Class1.lock) {
            holder.assertMadness();
        }
    }
    

    【讨论】:

    • 为什么要把锁设为静态??
    • 您提供了 2 种安全机制:实例限制和 volatile 关键字。我的问题是,代码将如何单独处理每个解决方案?
    • volatile 表现得好像没有其他东西可以触及它。同步也是如此。但是,如果两个事物没有同步在一起,并且某些事物试图接触已经在使用的事物,则会导致试图接触已经在使用的 volatile 变量的事物发生挂起/死锁。它是一个概念,它的文档很少,无论你找到什么,都是神秘的,因为它是一个神秘的概念。只是坐在那里想一分钟。任何两件事情都不应该同时对一件事情进行 I/O。用这些关键字防止它。
    • @JanOssowski 没必要,但如果您使用代理并尝试在同一 JVM 中的两个进程之间进行同步,它会派上用场。虽然此时你仍然可以使用反射,但它更容易
    • 乔,你刚刚说过,使用 volatile 忽略同步会出现死锁,我真的无法想象。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 2021-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多