如果线程A在java中的线程B之前启动,则A将在B之前由os调度?

时间:2016-08-20 08:10:21

标签: java multithreading happens-before

我正在阅读“了解JVM高级功能和最佳实践”,其中包含一个代码段,用于解释java中的事先规则。我不明白。代码如下:

private int value = 0;
//executed by Thread A
public void setValue(int value){
    this.value = value;
}
//executed by Thread B
public void getValue(){
    return value;
}

假设线程A在代码中的线程B之前开始。我可以理解,我们不知道线程B中getValue()返回的结果,因为它不是线程安全的。但该书说如果将同步关键字添加到函数setValue()getValue(),则不存在线程安全问题,方法getValue()将返回正确的值。这本书解释说,因为synchronized符合先前发生的规则。所以我在下面的代码中有两个问题。

public class VolatileDemo3 {
    private volatile int value = 0;
    public static void main(String[] args) {
        VolatileDemo3 v = new VolatileDemo3();
        Thread A = new Thread(v.new Test1());// Thread A
        Thread B = new Thread(v.new Test2());//Thread B
        A.start();  
        B.start();
    }
    public void setValue(int value){
        this.value  = value;
    }
    public int getValue(){
        return this.value;
    }

    public class Test1 implements Runnable {
        @Override
        public void run() {
            setValue(10);
        }   
    }
    public class Test2 implements Runnable {
        @Override
        public void run() {
            int v = getValue();
            System.out.println(v);
        }
    }
}
  1. 虽然A.start()B.start()之前运行且值为volatile,但我们无法确保主题B可以打印出10,对吧?因为线程B可能首先由JVM调度,所以线程B将打印0而不是10。
  2. 即使线程A在线程B之前由JVM调度,但我们也无法保证JVM在this.value = value之前执行的指令return this.value因为JVM将排序再次说明。我的理解是对的吗?请帮帮我。

2 个答案:

答案 0 :(得分:9)

“之前发生”的问题并不是它导致线程A在线程B之前设置值。虽然可能发生线程A在线程B运行之前按时间顺序到达onPause this.value = value,B看到的值可能仍然是旧值。

也就是说,在线程环境中,即使按时间顺序执行两条指令,也不意味着另一条指令的结果会被另一条指令看到。

如果线程B碰巧首先调用该方法,它将始终获得旧值。但是,如果碰巧将方法称为第二个,那么它是否会获得旧值或新值是未知的。

出于这个原因,你必须使用手段来确保“之前发生”规则,然后你知道“之前发生的事情”的结果可以通过“发生之后”看到。

因此,如果getValue是易失性的,它确保如果线程B之前的线程A调用value,则线程B将看到新值。

╔═════════════════════╤════════════════════════╤═════════════════════╗
║ Order of operations │ Are we using           │ What value of value ║
║                     │ volatile/synchronized? │ will B see?         ║
╠═════════════════════╪════════════════════════╪═════════════════════╣
║ A runs setValue(10) │ N                      │ Unknown             ║
║ B runs getValue()   ├────────────────────────┼─────────────────────╢
║                     │ Y                      │ 10                  ║
╟─────────────────────┼────────────────────────┼─────────────────────╢
║ B runs getValue()   │ N                      │ 0                   ║
║ A runs setValue(10) ├────────────────────────┼─────────────────────╢
║                     │ Y                      │ 0                   ║
╚═════════════════════╧════════════════════════╧═════════════════════╝

关于你的两个问题:

  1. 真。您无法知道他们中的哪一个首先获得该指令。这不仅仅是首先安排哪个线程的问题。线程可能在不同的CPU上运行,一个CPU可能需要长内存提取,另一个CPU只需要很短的内存提取,因此它比另一个慢。而且,准备代码的机器指令可能具有不同的长度。一般来说,你根本不知道幕后发生了什么,而且Java不能保证线程的运行顺序。
  2. 在这种特殊情况下,不太可能重新安排说明,因为这些方法非常短。同样,你无法分辨出发生了什么,因为它取决于特定的JVM,CPU的数量,CPU的类型,调度程序和内存安排 - 你无法保证。

答案 1 :(得分:0)

将synchronized添加到函数setValue/getValue意味着任何想要执行该段代码的线程首先必须获取(或等待)该对象的锁定。

如果我们假设在线程A调用setValue / getValue之前没有锁定,则线程A将立即获得锁定。但是,在过渡期间,如果线程B调用setValue/getValue,它必须等待线程A放弃锁定才能执行该方法。

但是,如果两个线程都在等待对象的锁定,我们无法保证操作系统首先选择哪一个。