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

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


当前回答

Volatile具有内存可见性的语义。基本上,volatile字段的值在写操作完成后对所有读取器(特别是其他线程)可见。如果没有volatile,读者可以看到一些未更新的值。

回答您的问题:是的,我使用一个volatile变量来控制某些代码是否继续循环。循环测试易变值,如果为真则继续。可以通过调用“stop”方法将条件设置为false。循环看到false,并在stop方法完成执行后测试该值时终止。

我强烈推荐的《Java并发实践》一书对volatile做了很好的解释。这本书的作者与问题中提到的IBM文章的作者是同一人(事实上,他在那篇文章的末尾引用了他的书)。我对volatile的使用被他的文章称为“模式1状态标志”。

如果您想了解更多关于volatile在底层是如何工作的,请阅读Java内存模型。如果你想超越这个层次,看看像Hennessy & Patterson这样的优秀计算机体系结构书籍,阅读关于缓存一致性和缓存一致性的内容。

其他回答

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

挥发性(vɒlətʌɪl):在常温下容易挥发

关于volatile的重要一点:

Synchronization in Java is possible by using Java keywords synchronized and volatile and locks. In Java, we can not have synchronized variable. Using synchronized keyword with a variable is illegal and will result in compilation error. Instead of using the synchronized variable in Java, you can use the java volatile variable, which will instruct JVM threads to read the value of volatile variable from main memory and don’t cache it locally. If a variable is not shared between multiple threads then there is no need to use the volatile keyword.

volatile用法示例:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

我们在第一个请求到来时惰性地创建实例。

如果我们不使_instance变量为volatile,那么创建Singleton实例的线程就不能与其他线程通信。因此,如果线程A正在创建单例实例,在创建后,CPU损坏等,所有其他线程将无法看到_instance的值不为空,他们将认为它仍然被分配为空。

为什么会发生这种情况?因为读线程不做任何锁,直到写线程从同步块中出来,内存不会被同步,_instance的值也不会在主存中更新。使用Java中的Volatile关键字,这是由Java本身处理的,这样的更新将对所有读取线程可见。

结论:volatile关键字也用于线程之间的内存内容通信。

without volatile的用法示例:

public class Singleton {    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance() {   
        if (_instance == null) {  
            synchronized(Singleton.class) {  
                if (_instance == null) 
                    _instance = new Singleton(); 
            } 
        }
        return _instance;  
    }
}

The code above is not thread-safe. Although it checks the value of instance once again within the synchronized block (for performance reasons), the JIT compiler can rearrange the bytecode in a way that the reference to the instance is set before the constructor has finished its execution. This means the method getInstance() returns an object that may not have been initialized completely. To make the code thread-safe, the keyword volatile can be used since Java 5 for the instance variable. Variables that are marked as volatile get only visible to other threads once the constructor of the object has finished its execution completely. Source

Java中不稳定的用法:

快速失败迭代器通常使用list对象上的volatile计数器实现。

当列表更新时,计数器会递增。 创建Iterator时,计数器的当前值嵌入到Iterator对象中。 当执行Iterator操作时,该方法比较两个计数器值,如果不相同则抛出ConcurrentModificationException异常。

故障安全迭代器的实现通常是轻量级的。它们通常依赖于特定列表实现的数据结构的属性。没有一般的模式。

下面是一个非常简单的代码,演示了volatile for variable的需求,它用于从其他线程控制线程执行(这是需要volatile的一个场景)。

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

当不使用volatile时:即使在“stop on: xxx”之后,你也永远不会看到“Stopped on: xxx”消息,并且程序继续运行。

Stopping on: 1895303906650500

当使用volatile时:你会立即看到'Stopped on: xxx'。

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

演示:https://repl.it/repls/SilverAgonizingObjectcode

从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关键字有两种不同的用法。

阻止JVM从寄存器(假设为缓存)读取值,并强制从内存读取其值。 降低内存不一致错误的风险。

阻止JVM读取寄存器中的值,并强制其读取 从内存中读取的值。

busy标志用于防止线程在设备繁忙且该标志不受锁保护的情况下继续运行:

while (busy) {
    /* do something else */
}

当另一个线程关闭busy标志时,测试线程将继续执行:

busy = 0;

但是,由于busy在测试线程中经常被访问,JVM可以通过将busy的值放在寄存器中来优化测试,然后在每次测试之前测试寄存器的内容,而不读取内存中的busy值。测试线程永远不会看到busy的变化,而另一个线程只会改变内存中的busy值,从而导致死锁。将busy标志声明为volatile将强制在每次测试之前读取它的值。

降低内存一致性错误的风险。

使用易失性变量可以降低内存一致性错误的风险,因为对易失性变量的任何写入都会建立一个 “happens-before”关系与该变量的后续读取之间的关系。这意味着对volatile变量的更改总是对其他线程可见。

这种读写而不产生记忆一致性错误的技术被称为原子动作。

原子作用是指有效地同时发生的作用。原子的动作不可能中途停止:它要么完全发生,要么根本不发生。在操作完成之前,原子操作的副作用是不可见的。

下面是你可以指定的原子操作:

对于引用变量和大多数变量来说,读和写都是原子的 基本变量(所有类型,除了long和double)。 对于所有声明为volatile的变量,读和写都是原子的 (包括长变量和双变量)。

干杯!