5

我正在阅读《Understanding the JVM Advanced Features and Best practice》,其中有一段代码解释了java中的happens-before规则。我无法理解。代码如下:

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我可以理解,我们不知道getValue()线程 B 中返回的结果,因为它不是线程安全的。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()run beforeB.start()和 value is volatile,但我们不能确保线程 B 可以打印出来10,对吧?因为线程 B 可能首先被 JVM 调度,所以线程 B 将打印 0 而不是 10。
  2. 即使A线程被 JVM 调度在线程之前B,但我们也不能保证this.value = value之前被 JVM 执行的指令,return this.value因为 JVM 会再次对指令进行排序。我的理解是对的吗?请帮我。
4

2 回答 2

9

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

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

如果线程 B 碰巧先调用了该方法,它将始终获取旧值。但是如果它碰巧第二次调用了该方法,那么它是获取旧值还是新值是未知的。

为此,您必须使用手段来确保“发生在之前”的规则,然后您才能知道“发生在之前”的结果被“发生在之后”的结果中看到。

因此value,例如 if 是 volatile ,它确保 ifsetValue()在线程 B 之前由线程 A 调用,则线程 B 将看到新值。

╔═════════════════════╤════════════════════════╤══ ═══════════════════╗
║ 运算顺序│ 我们用的是什么 │ value 的是什么║
║│易失/同步?│ B 会看到吗?║
╠═════════════════════╪════════════════════════╪══ ═══════════════════╣
║ A 运行setValue(10) │ N │ Unknown ║
║ B 运行getValue()    ├────────────────────────┼────────────────── ────╢
║ │ 是 │ 10 ║
╟──────────────────────┼────────────────────────┼── ────────────────────╢
║ B 运行getValue()    │ N │ 0 ║
║ A 运行setValue(10) ├──────────────────────┼───────────────── ────╢
║ │ 是 │ 0 ║
╚═════════════════════╧════════════════════════╧══ ═══════════════════╝

关于你的两个问题:

  1. 真的。你不知道他们中的哪一个先得到那个指令。这不仅仅是首先安排哪个线程的问题。线程可能在不同的 CPU 上运行,一个 CPU 可能需要长内存获取,另一个 CPU 只需要短内存获取,因此它比另一个慢。此外,准备代码的机器指令可能具有不同的长度。通常,您根本不知道幕后发生了什么,Java 不保证线程运行的顺序。
  2. 在这种特殊情况下不太可能重新安排指令,因为方法非常短。同样,您无法知道发生了什么,因为这取决于特定的 JVM、CPU 数量、CPU 类型、调度程序和内存安排——您无法保证。
于 2016-08-20T08:18:28.763 回答
0

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

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

但是,如果两个线程都在等待对象上的锁,我们不能保证哪个线程会首先被操作系统选中。

于 2016-08-20T08:20:59.173 回答