Java:易失性隐含订单保证

Java:易失性隐含订单保证,java,concurrency,volatile,java-memory-model,Java,Concurrency,Volatile,Java Memory Model,我的问题是这个问题的延伸: 为了使其更具体,假设我们有一个简单的类,它在初始化后可以处于两种状态: class A { private /*volatile?*/ boolean state; private volatile boolean initialized = false; boolean getState(){ if (!initialized){ throw new IllegalStateException();

我的问题是这个问题的延伸:

为了使其更具体,假设我们有一个简单的类,它在初始化后可以处于两种状态:

class A {
    private /*volatile?*/ boolean state;
    private volatile boolean initialized = false;

    boolean getState(){
        if (!initialized){
            throw new IllegalStateException();
        }
        return state;
    }

    void setState(boolean newState){
        state = newState;
        initialized = true;
    }
}
初始化的字段声明为volatile,因此它引入了before before“barrier”,以确保不会发生重新排序。由于状态字段在写入初始化字段之前是只写的,而在读取初始化字段之后是只读的,因此我可以从状态声明中删除volatile关键字,仍然看不到过时的值。问题是:

  • 这个推理正确吗
  • 是否保证写入初始化字段不会被优化掉(因为它只在第一次更改)并且“屏障”不会丢失
  • 假设使用倒计时闩锁作为初始值设定项而不是标志,如下所示:

    class A {
        private /*volatile?*/ boolean state;
        private final CountDownLatch initialized = new CountDownLatch(1);
    
        boolean getState() throws InterruptedException {
            initialized.await();
            return state;
        }
    
        void setState(boolean newState){
            state = newState;
            initialized.countdown();
        }
    }
    
    还可以吗

  • 一,。这个推理正确吗

    否,状态将缓存在线程中,因此您无法获取最新值

    二,。是否保证写入初始化字段不会被删除 优化了(因为它只在第一次更改)和 “屏障”不会丢失吗

    三,。假设使用倒计时锁存器而不是标志作为 像这样的初始化器

    正如@ratchet freak提到的,CountDownLatch是一次锁存,而volatile是一种可重用的锁存,所以第三个问题的答案应该是:如果你要多次设置状态,你应该使用volatile

    你的代码(大部分)正确,这是一个常见的习语

    // reproducing your code
    class A
    
        state=false;              //A
        initialized=false;        //B
    
        boolean state;
        volatile boolean initialized = false;        //0
    
        void setState(boolean newState)
            state = newState;                        //1
            initialized = true;                      //2
    
        boolean getState()
            if (!initialized)                        //3
                throw ...;
            return state;                            //4
    
    第#A#B行是用于将默认值写入变量的伪代码(也称为字段归零)。我们需要将它们纳入严格的分析。注意#B不同于#0;两者都被执行。第#B行不被视为易失性写入

    所有变量上的所有易失性访问(读/写)都是按总顺序进行的。我们想确定,如果达到4,那么在这个顺序中,2在3之前

    有3次写入
    已初始化
    :#B、#0和#2。只有#2赋值为真。因此,如果#2在#3之后,#3不能读为真(这可能是由于我不完全理解的无中生有的保证),那么#4就不能读到

    因此,如果达到#4,则#2必须在#3之前(按照易失性访问的总顺序)

    因此#2发生在#3之前(易失性写入发生在后续易失性读取之前)

    按照编程顺序,#1发生在#2之前,#3发生在#4之前

    根据及物性,因此#1发生在#4之前

    第#A行,默认写入,发生在所有操作之前(其他默认写入除外)

    因此,对变量
    状态的所有访问都处于一个发生在链之前的状态:#a->#1->#4。没有数据竞争。程序已正确同步。读#4必须遵守写#1

    不过有一个小问题。行#0显然是多余的,因为#B已经赋值为false。实际上,易失性写操作对性能的影响不容忽视,因此我们应该避免#0

    更糟糕的是,#0的存在可能会导致不期望的行为:#0可能发生在#2之后!因此,可能会调用
    setState()
    ,但随后的
    getState()
    会不断抛出错误

    如果对象未安全发布,这是可能的。假设线程T1创建并发布对象;线程T2获取对象并对其调用
    setState()
    。如果发布不安全,T2可以在T1完成初始化对象之前观察对对象的引用

    如果要求安全发布所有
    A
    对象,则可以忽略此问题。这是一个合理的要求。它可以隐含地被期望

    但如果我们没有第0行,这根本就不会有问题。默认写入#B必须发生在#2之前,因此只要调用
    setState()
    ,所有后续的
    getState()
    都将遵守
    initialized==true


    在倒计时锁存器示例中,
    初始化
    最终
    ;这对于保证安全发布至关重要:所有线程都将观察到正确初始化的闩锁。

    在第二种情况下,它将在第一次时可见,但是第一次之后的新更新不保证可见。除非闩锁为空时不会发生获取。(如果我错了,请纠正我)当
    count()==0
    setState(…)
    首先修改非易失性变量,然后对
    初始化的
    执行易失性写入时,文档说
    等待
    倒计时
    方法是不可操作的。在另一个线程对初始化的
    执行volatile读取后,可以保证(根据JVM规范第17章),volatile写入之前的所有修改在volatile读取之后对线程可见,而无需进一步同步(或volatile)。为了澄清这一点,第二次调用setState时,它将首先更改状态变量。如果此时,在写入volatile之前,另一个线程调用getState(),他将看到初始化的变量为true,然后读取状态变量,其中他可能仍然看到state.JB Nizet的旧值,第二次调用setState时,它将执行新的volatile写入(写入相同的值,但仍然是易失性写入)。在此之后读取的任何电压都将保证读取
    状态
    将反映主内存中的当前值。如果一个线程在写入
    状态
    和另一个线程的易失性写入之间读取
    状态
    ,则有两种可能性:它将读取旧值(这与之前对getState()的调用完全相同