什么是Java中的易失性以及何时/应该如何使用它们?

时间:2015-07-28 04:03:41

标签: java multithreading volatile

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

的共同点
  

保证不会被缓存以及不同的线程   将看到更新后的值

但是,从上面的例子来看,线程有不同的值(旧/缓存值),还是因为实现不当?

1 个答案:

答案 0 :(得分:2)

标记变量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,因为它是线程对象,这意味着它们有两个,因此它们不会在您需要时相互阻塞。 / p>

你可以通过一些kludge解决这个问题,在线程类中引入一个静态对象:

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);
}