【问题标题】:Volatile happens-before relationship when there's mix of volatile and non-volatile fields易失性和非易失性字段混合时的易失性发生前关系
【发布时间】:2020-07-01 03:38:03
【问题描述】:

当混合使用易失性和非易失性字段时,我试图了解易失性字段的发生前行为。

假设有 1 个 WriteThread 和 5 个 ReadThreads,它们更新/读取 SharedObject。

ReadThreads 从头开始​​调用方法waitToBeStopped(),WriteThread 在1 秒后调用方法stop()

public class SharedObject {

    volatile boolean stopRequested = false;
    int a = 0, b = 0, c = 0;
    int d = 0, e = 0, f = 0;

    // WriteThread calls this method
    public void stop() {
        a = 1;  
        b = 2;  
        c = 3;
        stopRequested = true;
        a = 4;  
        b = 5;
        c = 6; 
        d = 7;
        e = 8;
        f = 9;
    }

    // ReadThread calls this method
    public void waitToBeStopped() throws Exception {
        
        while(!stopRequested) {
        }
        System.out.println("Stopped now.");

        System.out.println(a + " " + b + " " + c + " " + d + " " + e + " " + f);
    }
}

当这个程序结束时,输出是这样的。即使我尝试了 100 多个 ReadThreads,结果也始终相同。

Stopped now.
Stopped now.
Stopped now.
Stopped now.
Stopped now.
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9

第一季度。谁能解释为什么这总是返回 4,5,6,7,8,9 而不是 1,2,3,0,0,0?

我对happens-before关系的理解是这样的:

  • WriteThread 写入a=1,b=2,c=3 发生在 WriteThread 写入stopRequested
  • WriteThread 写入stopRequested 发生在 WriteThread 写入a=4,b=5,c=6,d=7,e=8,f=9
  • WriteThread 写入stopRequested 发生在 ReadThread 读取stopRequested
  • ReadThread 读取 stopRequested 发生在 ReadThread 读取 a,b,c,d,e,f

从这 4 个陈述中,我无法得出这样一个……

  • WriteThread 写入a=4,b=5,c=6,d=7,e=8,f=9 发生在 ReadThread 读取a,b,c,d,e,f

如果有帮助,这里是代码的另一部分:

public class App {
    public static void main(String[] args) throws Exception {
        SharedObject sharedObject = new SharedObject();

        for(int i =0 ; i < 5; i++) {
            Runnable rThread = new ReadThread(sharedObject);
            new Thread(rThread).start();
        }
        Runnable wThread = new WriteThread(sharedObject);
        
        new Thread(wThread).start();        

    }
}
public class WriteThread implements Runnable {

    private SharedObject sharedObject;
    
    public WriteThread(SharedObject sharedObject) {
        this.sharedObject = sharedObject;
    }
    
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1);
            sharedObject.stop();
                        
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
public class ReadThread implements Runnable {

    private SharedObject sharedObject;
    
    public ReadThread(SharedObject sharedObject) {
        this.sharedObject = sharedObject;
    }
    
    public void run() {
        try {
            sharedObject.waitToBeStopped();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }   
}

【问题讨论】:

  • 嘿!你也可以发布整个测试课程吗?您是否考虑过设置 6 个整数可能比打印到控制台快得多?
  • 嘿@akuzminykh 我刚刚也分享了其他课程。嗯,我不明白的是,如果 4、5、6、7、8、9 是在写入 volatile 字段(stopRequested)之后写入的,那么更改是如何进行的?这里的顺序不重要吗?

标签: java multithreading volatile happens-before


【解决方案1】:

您认为stopRequested = true; 之后的写入不保证对读者可见的假设是正确的。编写者没有保证将这些写入写入共享缓存/内存,在这些缓存/内存中它们对读者可见。它可以将它们写入本地缓存,而读者不会看到更新的值。

Java 语言保证可见性,例如当您使用 volatile 变量时。但它保证-volatile变量的变化不会对其他线程可见。在您的情况下,此类写入仍然可以看到。 JVM 实现、处理器的内存一致性模型等方面影响可见性。

请注意,JLS 和happens-before 关系是一个规范。 JVM 实现和硬件通常比 JLS 指定的更多,这可能导致 JLS 不必看到的写入可见性。

【讨论】:

  • 非常感谢!这很有帮助。这些行为很难测试..
  • @Ryan True。这就是为什么很难进行正确的同步。当没有指标时很难发现问题......突然它会显示在另一台机器上或当您不期望它时。这就是为什么了解volatile 的实际作用如此重要的原因。查看this了解更多详情。
猜你喜欢
  • 2018-01-18
  • 1970-01-01
  • 1970-01-01
  • 2023-02-10
  • 2015-11-07
  • 2023-03-21
  • 2019-11-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多