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

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


当前回答

The volatile key when used with a variable, will make sure that threads reading this variable will see the same value . Now if you have multiple threads reading and writing to a variable, making the variable volatile will not be enough and data will be corrupted . Image threads have read the same value but each one has done some chages (say incremented a counter) , when writing back to the memory, data integrity is violated . That is why it is necessary to make the varible synchronized (diffrent ways are possible)

如果修改是由一个线程完成的,而其他线程只需要读取这个值,则volatile将是合适的。

其他回答

是的,我经常使用它——它对多线程代码非常有用。你指的那篇文章很好。不过有两件重要的事情要记住:

You should only use volatile if you completely understand what it does and how it differs to synchronized. In many situations volatile appears, on the surface, to be a simpler more performant alternative to synchronized, when often a better understanding of volatile would make clear that synchronized is the only option that would work. volatile doesn't actually work in a lot of older JVMs, although synchronized does. I remember seeing a document that referenced the various levels of support in different JVMs but unfortunately I can't find it now. Definitely look into it if you're using Java pre 1.5 or if you don't have control over the JVMs that your program will be running on.

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

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

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

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

挥发性

volatile -> synchronized[关于]

Volatile表示对于程序员来说,该值总是最新的。问题是该值可以保存在不同类型的硬件内存中。例如,它可以是CPU寄存器,CPU缓存,RAM…СPU寄存器和CPU缓存属于CPU,不能共享数据,不像RAM在多线程环境中处于抢救状态

volatile关键字表示变量将直接从/写入RAM内存。它有一些计算足迹

Java 5通过支持happens-before扩展volatile[关于]

对volatile字段的写入发生在后续每次读取该字段之前。

Read is after write

volatile关键字不能修复竞态条件[关于],使用synchronized关键字[关于]

因此,只有当一个线程写入,而其他线程只是读取volatile值时才安全

Volatile执行以下操作。

不同线程对volatile变量的读写总是从内存,而不是从线程自己的缓存或cpu寄存器。所以每个线程总是处理最新的值。 2>当两个不同的线程在堆中使用相同的实例或静态变量时,其中一个线程可能会认为其他线程的操作是无序的。请看jeremy manson的博客。但不稳定在这里有所帮助。

下面完全运行的代码展示了如何在不使用synchronized关键字的情况下以预定义的顺序执行多个线程并打印输出。

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

为了实现这一点,我们可以使用以下完整的运行代码。

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

下面的github链接有一个自述,它给出了适当的解释。 https://github.com/sankar4git/volatile_thread_ordering

挥发性(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异常。

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