【问题标题】:What is Volatile in Java and when/how should we use them? [duplicate]Java 中的 Volatile 是什么,我们应该何时/如何使用它们? [复制]
【发布时间】:2015-07-28 04:03:41
【问题描述】:
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 的共同点

保证它不会被缓存并且不同的线程 将看到更新后的值

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

【问题讨论】:

  • 你看过Atomic Access吗?
  • @MadProgrammer 它说对 volatile 变量的更改始终对其他线程可见
  • 是的,确实如此,但问题仍然是这些更改何时真正提交......
  • @MadProgrammer 你的意思是,我们永远无法确定增量是否完成。我对吗 ?如果是这样,那么我们永远无法预测多线程环境中的 volatile 变量值。对吗?
  • 差不多,链接的答案对此有一些有趣的观点,因此您可以同时使用synchronizedvolatile 或使用Atomic API,它为您提供了一些其他选项跨度>

标签: java multithreading volatile


【解决方案1】:

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

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

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

【讨论】:

  • 请纠正我,那我为什么要使用 volatile ?如果我要同步,那么我可以使用简单的非易失性变量。这将导致相同的结果。我说的对吗?
  • 另外,我按照你说的做了更改,我仍然看到相同的值模式。
  • @jWeaver synchronised 将防止被修改,但如果数据被缓存则不会,因为 volatile 应该防止值被缓存并为您提供实时访问,这是一种平衡行为。最近,我在 volatile 上使用了 Atomc
  • 我更喜欢同步操作,因为在很多情况下 volatile 都无济于事。举个例子,当你想要一个像你一样的 print-modify-print 值时,这是不好的。事实上,我似乎记得它不适合根据旧值修改变量,因为 Java 不保证 x++ 始终是原子的。
  • @jWeaver,为此道歉,事实证明 this 在您的情况下是两个不同的线程对象之一,因此它们不会同步。修改后的答案来解决这个问题。
猜你喜欢
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
  • 2022-01-07
  • 2014-08-31
  • 1970-01-01
  • 2012-04-12
  • 2011-05-25
  • 1970-01-01
相关资源
最近更新 更多