Java 和 when/how 中的 Volatile 是什么,我们应该使用它们吗?

What is Volatile in Java and when/how should we use them?

public class Volatile {

    volatile int x = 0;

    public static void main(String a[]) {
        Volatile y = new Volatile();
        test t1 = new test(y);
        test t2 = new test(y);
        t1.setName("A");
        t2.setName("B");
        t1.start();
        t2.start();
    }
}

class test extends Thread {

    Volatile v;

    test(Volatile v) {
        this.v = v;
    }

    @Override
    public void run() {
        for (int i = 0; i < 4; i++) {
            System.out.println(Thread.currentThread().getName() + "Says Before " + v.x);
            v.x++;
            System.out.println(Thread.currentThread().getName() + "Says After " + v.x);
        }
    }
}

输出

ASays Before 0
BSays Before 0
BSays After 2
BSays Before 2
BSays After 3
ASays After 1   <--- Is it a cache value ?
BSays Before 3
ASays Before 3
BSays After 4
BSays Before 5
BSays After 6
ASays After 5    <--- Is it a cache value ?
ASays Before 6
ASays After 7
ASays Before 7
ASays After 8

我到处都发现了关于 volatile 的共同点

guaranteeing that it will not be cached and that different threads will see the updated value

但是,从我上面的例子来看,线程有不同的值(old/cache 值)还是因为实施不当?

将变量标记为 volatile 将阻止 JVM 缓存该值,但它 不会 为您处理同步问题(例如在修改变量之间换出线程)并打印出来)。

例如,线程 A 输出 before 0 然后被换出,以便线程 B 运行。该值仍然为零,因为 A 尚未更新它。 B 然后更新它,然后 A 返回并更新它,然后打印它。这意味着您最终可能会得到类似的结果:

ASays Before 0
BSays Before 0
ASays After 2

不太理想。

此外,println 本身不是原子的,所以它可以在流中途被中断,导致线程持有一个过时的打印值(即稍后出现在输出流中的值)那它会是理想的)。

要正确更新和打印,您应该在需要自动使用变量的块周围使用 synchronized,例如:

synchronized (this) {
    System.out.println(Thread.currentThread().getName() + " before " + v.x);
    v.x++;
    System.out.println(Thread.currentThread().getName() + " after " + v.x);
}

然而,在您的特定情况下,您不能使用 this,因为它是线程对象,这意味着它们有 两个 ,因此它们不会互相阻塞如你所愿。

你可以通过一点点麻烦来解决这个问题,在线程中引入一个静态对象 class:

static Object o = new Object();

并将其用于同步:

synchronized (o) {
    System.out.println(Thread.currentThread().getName() + " before " + v.x);
    v.x++;
    System.out.println(Thread.currentThread().getName() + " after " + v.x);
}