如果在 java 中线程 A 在线程 B 之前启动,那么 A 将在 B 之前被 os 调度?

if thread A start before thread B in java,then A will be scheduled by os before B?

我正在阅读《理解 JVM 高级特性和最佳实践》,其中有一段代码解释了 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 之前启动。我可以理解,我们不知道线程 B 中 getValue() 编辑的结果 return,因为它不是线程安全的。但是书上说如果给函数setValue()getValue()加上synchronized关键字,就不存在线程安全问题,方法getValue()会return正确的值。书上解释说因为 synchronized 符合 happens-before 规则。所以我通过下面的代码有两个问题。

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被JVM调度在线程B之前,但是我们也不能保证指令this.value = valuereturn this.value之前被JVM执行,因为JVM将再次对指令进行排序。我的理解对吗?请帮助我。

"happened before" 的问题不是它导致线程 A 在线程 B 之前设置值。而是尽管线程 A 可能会按时间顺序在线程 B 之前到达 this.value = value到了运行getValue,B看到的值可能还是原来的值

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

如果线程B刚好先调用该方法,它总是会得到旧值。但是如果恰好是第二次调用该方法,则不知道是获取旧值还是获取新值。

为此,你不得不用手段来确保"happens before"规则,然后你就知道什么"happened before"的结果被什么"happens after"看到了。

因此,如果 value 是易变的,例如,它确保如果线程 A 在线程 B 之前调用 setValue(),则线程 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. 没错。你不知道他们中的哪一个先得到那个指令。这不仅仅是先调度哪个线程的问题。线程可能 运行ning 在不同的 CPUs 上,一个 CPU 可能需要长时间的内存获取,另一个只需要很短的内存获取,所以它比另一个慢。此外,可能是为代码准备的机器指令的长度不同。通常,您根本不知道幕后发生了什么,并且 Java 不保证线程 运行.
  2. 的顺序
  3. 在这种特殊情况下不太可能重新安排说明,因为这些方法非常短。同样,您无法判断发生了什么,因为它取决于特定的 JVM、CPU 的数量、CPU 的类型、调度程序和内存安排——您无法保证。

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

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

但是,如果两个线程都在等待对象上的锁,我们无法保证 OS 首先选择哪个线程。