今天工作时,我在Java中遇到了volatile关键字。不是很熟悉,我找到了这个解释。

鉴于那篇文章详细解释了所讨论的关键字,您曾经使用过它吗?或者您曾经看到过以正确方式使用该关键字的情况吗?


当前回答

Volatile对于停止线程非常有用。

并不是说您应该编写自己的线程,Java 1.6有很多不错的线程池。但是如果你确定你需要一个线程,你需要知道如何停止它。

我使用的线程模式是:

public class Foo extends Thread {

  private volatile boolean close = false;

  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

在上面的代码段中,while循环中读取close的线程与调用close()的线程不同。如果没有volatile,运行循环的线程可能永远看不到关闭的更改。

注意,这里不需要同步

其他回答

从oracle文档页,需要volatile变量来修复内存一致性问题:

使用易失性变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会与该变量的后续读取建立happens-before关系。

这意味着对volatile变量的更改总是对其他线程可见。这也意味着当线程读取一个volatile变量时,它不仅看到了volatile的最新更改,还看到了导致更改的代码的副作用。

正如在Peter Parker的回答中解释的那样,在没有volatile修饰符的情况下,每个线程的堆栈都可能有自己的变量副本。通过将变量设置为volatile,可以修复内存一致性问题。

为了更好地理解,请查看jenkov教程页面。

看一下相关的SE问题,了解更多关于volatile的细节和使用volatile的用例:

Java中volatile和synchronized的区别

一个实际的用例:

你有很多线程,它们需要以特定的格式打印当前时间,例如:java.text.SimpleDateFormat("HH-mm-ss")。可以有一个类,它将当前时间转换为SimpleDateFormat,并每一秒更新一次变量。所有其他线程都可以使用这个易失性变量在日志文件中打印当前时间。

Volatile变量是轻量级同步。当所有线程之间的最新数据可见性是必需的,并且原子性可能会受到损害时,在这种情况下,Volatile变量必须是首选。对volatile变量的读取总是返回任何线程最近完成的写操作,因为它们既不缓存在寄存器中,也不缓存在其他处理器看不到的缓存中。Volatile是无锁的。当场景满足上面提到的条件时,我使用volatile。

虽然我在这里提到的答案中看到了许多很好的理论解释,但我在这里添加了一个实际的例子来解释:

1.

代码在不使用volatile的情况下运行

public class VisibilityDemonstration {

private static int sCount = 0;

public static void main(String[] args) {
    new Consumer().start();
    try {
        Thread.sleep(100);
    } catch (InterruptedException e) {
        return;
    }
    new Producer().start();
}

static class Consumer extends Thread {
    @Override
    public void run() {
        int localValue = -1;
        while (true) {
            if (localValue != sCount) {
                System.out.println("Consumer: detected count change " + sCount);
                localValue = sCount;
            }
            if (sCount >= 5) {
                break;
            }
        }
        System.out.println("Consumer: terminating");
    }
}

static class Producer extends Thread {
    @Override
    public void run() {
        while (sCount < 5) {
            int localValue = sCount;
            localValue++;
            System.out.println("Producer: incrementing count to " + localValue);
            sCount = localValue;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                return;
            }
        }
        System.out.println("Producer: terminating");
    }
}
}

在上面的代码中,有两个线程——生产者和消费者。

生产者线程在循环中迭代5次(睡眠时间为1000毫秒或1秒)。在每次迭代中,生产者线程将sCount变量的值增加1。因此,在所有迭代中,生产者将sCount的值从0更改为5

使用者线程处于一个常量循环中,每当sCount的值发生变化时,它就会打印,直到值达到5为止。

两个循环同时开始。因此,生产者和消费者都应该将sCount的值打印5次。

输出

Consumer: detected count change 0
Producer: incrementing count to 1
Producer: incrementing count to 2
Producer: incrementing count to 3
Producer: incrementing count to 4
Producer: incrementing count to 5
Producer: terminating

分析

In the above program, when the producer thread updates the value of sCount, it does update the value of the variable in the main memory(memory from where every thread is going to initially read the value of variable). But the consumer thread reads the value of sCount only the first time from this main memory and then caches the value of that variable inside its own memory. So, even if the value of original sCount in main memory has been updated by the producer thread, the consumer thread is reading from its cached value which is not updated. This is called VISIBILITY PROBLEM .

2.

代码使用volatile运行

在上面的代码中,用下面的代码替换声明了sCount的代码行:

private volatile  static int sCount = 0;

输出

Consumer: detected count change 0
Producer: incrementing count to 1
Consumer: detected count change 1
Producer: incrementing count to 2
Consumer: detected count change 2
Producer: incrementing count to 3
Consumer: detected count change 3
Producer: incrementing count to 4
Consumer: detected count change 4
Producer: incrementing count to 5
Consumer: detected count change 5
Consumer: terminating
Producer: terminating

分析

当我们声明一个变量为volatile时,这意味着所有对这个变量的读写操作都将直接进入主存。这些变量的值永远不会被缓存。

由于sCount变量的值永远不会被任何线程缓存,消费者总是从主内存中读取sCount的原始值(在那里由生产者线程更新)。因此,在这种情况下,输出是正确的,两个线程都打印了5次不同的sCount值。

通过这种方式,volatile关键字解决了可见性问题。

如果您有一个多线程系统,这些多线程处理一些共享数据,这些线程将在它们自己的缓存中加载数据。如果我们不锁定资源,在一个线程中所做的任何更改都不会在另一个线程中可用。

使用锁定机制,我们可以添加对数据源的读/写访问。如果一个线程修改了数据源,该数据将存储在主存中而不是它的缓存中。当其他线程需要这个数据时,它们将从主存中读取它。这将显著增加延迟。

为了减少延迟,我们将变量声明为volatile。这意味着无论何时变量的值在任何处理器中被修改,其他线程都将被迫读取它。它仍然有一些延迟,但比从主存中读取要好。

…volatile修饰符保证任何读取字段的线程都能看到最近写入的值。——乔希·布洛赫 如果您正在考虑使用volatile,请仔细阅读java.util.concurrent包,它处理原子行为。 维基百科上关于单例模式的帖子显示了volatile的使用。