首页 文章

你在Java中使用volatile关键字吗?

提问于
浏览
547

在今天的工作中,我在Java中遇到了 volatile 关键字 . 我不太熟悉它,我发现了这个解释:

Java理论与实践:管理波动性

鉴于该文章解释了相关关键字的详细信息,您是否使用过它,或者您是否曾经看到过以正确方式使用此关键字的情况?

21 回答

  • 5

    当与变量一起使用时,volatile键将确保读取此变量的线程将看到相同的值 . 现在,如果您有多个线程读取和写入变量,使变量volatile变得不够,数据将被破坏 . 图像线程已经读取了相同的值,但每个都已经完成了一些操作(比如增加了一个计数器),当写回内存时,违反了数据完整性 . 这就是为什么有必要使变量同步(不同的方式是可能的)

    如果更改由1个线程完成而其他人只需读取此值,则volatile将是合适的 .

  • 151

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

    回答你的问题:是的,我使用 volatile 变量来控制某些代码是否继续循环 . 循环测试 volatile 值并继续,如果它是 true . 通过调用"stop"方法可以将条件设置为 false . 循环看到 false 并在stop方法完成执行后测试值时终止 .

    我强烈推荐的书“Java Concurrency in Practice”给出了 volatile 的一个很好的解释 . 本书由撰写问题中引用的IBM文章的同一人撰写(事实上,他引用了该文章底部的书) . 我对 volatile 的使用就是他的文章所谓的"pattern 1 status flag."

    如果您想了解有关volatile如何在幕后工作的更多信息,请阅读the Java memory model . 如果你想超越这个级别,请查看一本好的计算机体系结构书籍,如Hennessy & Patterson,并阅读缓存一致性和缓存一致性 .

  • 1

    “... volatile修饰符保证读取字段的任何线程都会看到最近写入的值 . ” - Josh Bloch

    如果您正在考虑使用 volatile ,请阅读处理原子行为的包java.util.concurrent .

    Singleton Pattern上的维基百科帖子显示使用不稳定 .

  • 12

    Important point about volatile:

    • 使用Java关键字 synchronizedvolatile 以及锁定可以实现Java中的同步 .

    • 在Java中,我们不能拥有 synchronized 变量 . 将 synchronized 关键字与变量一起使用是非法的,将导致编译错误 . 您可以使用java volatile 变量代替在Java中使用 synchronized 变量,该变量将指示JVM线程从主内存读取 volatile 变量的值,而不是在本地缓存它 .

    • 如果多个线程之间没有共享变量,则无需使用 volatile 关键字 .

    source

    Example usage of 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 实例的Thread无法与另一个线程通信 . 因此,如果线程A正在创建Singleton实例,并且在创建之后,CPU就会破坏等,所有其他线程将无法看到 _instance 的值为非null,并且他们将认为它仍然被赋值为null .

    为什么会这样?因为读取器线程没有执行任何锁定,并且在写入器线程从同步块中出来之前,内存将不会同步,并且 _instance 的值将不会在主内存中更新 . 使用Java中的Volatile关键字,这由Java本身处理,并且所有读取器线程都可以看到此类更新 .

    结论:volatile关键字也用于在线程之间传递内存的内容 .

    Example usage of 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;  
        }
    

    上面的代码不是线程安全的 . 虽然它在synchronized块中再次检查实例的值(出于性能原因),但JIT编译器可以重新排列字节码,方式是在构造函数完成执行之前设置对实例的引用 . 这意味着方法getInstance()返回一个可能尚未完全初始化的对象 . 为了使代码具有线程安全性,可以使用关键字volatile,因为Java 5用于实例变量 . 标记为volatile的变量只有在对象的构造函数完全执行后才能对其他线程可见 .
    Source

    enter image description here

    volatile usage in Java

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

    • 更新列表后,计数器会递增 .

    • 创建 Iterator 时,计数器的当前值嵌入在 Iterator 对象中 .

    • 执行 Iterator 操作时,方法比较两个计数器值,如果它们不同则抛出 ConcurrentModificationException .

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

  • 4

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

    注意不需要同步

  • 2

    使用 volatile 的一个常见示例是使用 volatile boolean 变量作为终止线程的标志 . 如果你已经启动了一个线程,并且你希望能够安全地从另一个线程中断它,你可以让线程定期检查一个标志 . 要停止它,请将标志设置为true . 通过使标志 volatile ,您可以确保检查它的线程将在下次检查它时设置它,而不必使用 synchronized 块 .

  • 0

    是的,只要您希望多个线程访问可变变量,就必须使用volatile . 它不是很常见的用例,因为通常需要执行多个原子操作(例如,在修改之前检查变量状态),在这种情况下,您将使用synchronized块 .

  • 87

    没有人提到长和双变量类型的读写操作的处理 . 读取和写入是参考变量和大多数原始变量的原子操作,但长变量和双变量类型除外,它们必须使用volatile关键字作为原子操作 . @link

  • 45

    使用 volatile 关键字声明的变量具有两个使其特殊的主要特性 .

    • 如果我们有一个volatile变量,它就不能被任何线程缓存到计算机的(微处理器)缓存中 . 访问总是发生在主内存中 .

    • 如果有一个 write operation 在一个volatile变量上,并且突然请求 read operation ,则保证 write operation will be finished prior to the read operation .

    以上两个品质推断出这一点

    • 读取volatile变量的所有线程肯定会读取最新值 . 因为没有缓存的值可以污染它 . 并且只有在当前写操作完成后才会授予读请求 .

    而另一方面,

    • 如果我们进一步调查我提到的 #2 ,我们可以看到 volatile 关键字是维护共享变量的理想方式,该变量有 'n' number of read threads and only one write thread 来访问它 . 一旦我们添加 volatile 关键字,就完成了 . 关于线程安全没有任何其他开销 .

    Conversly,

    我们 can't 仅使用 volatile 关键字来满足具有 more than one write threads accessing it 的共享变量 .

  • 9

    除了停止使用volatile关键字的线程之外,IMO有两个重要的场景

    • Double-checked locking mechanism . 经常用于Singleton设计模式 . 在这 singleton object needs to be declared volatile .

    • Spurious Wakeups . 即使没有发出通知呼叫,线程有时也会从等待呼叫中唤醒 . 这种行为称为supurious wakeup . 这可以通过使用条件变量(布尔标志)来抵消 . 只要标志为真,就将wait()调用放入while循环中 . 因此,如果由于除了notify / notifyall之外的任何原因线程从等待调用中唤醒,那么它遇到标志仍然是真的,因此再次调用wait . 在调用notify之前,将此标志设置为true . 在这种情况下 boolean flag is declared as volatile .

  • 28

    如果您正在开发多线程应用程序,则需要使用'volatile'关键字或'synchronized'以及您可能拥有的任何其他并发控制工具和技术 . 这种应用的示例是桌面应用 .

    如果您正在开发将部署到应用程序服务器(Tomcat,JBoss AS,Glassfish等)的应用程序,则您不必自己处理并发控制,因为应用程序服务器已经解决了这一问题 . 事实上,如果我记得正确,Java EE标准禁止在servlet和EJB中进行任何并发控制,因为它是“基础”层的一部分,你应该放弃它来处理它 . 如果要实现单例对象,则只在此类应用程序中执行并发控制 . 如果您使用像Spring这样的框架编织组件,这甚至已经解决了 .

    因此,在Java开发的大多数情况下,应用程序是Web应用程序并使用像Spring这样的IoC框架或EJB,您不需要使用'volatile' .

  • 3

    volatile 只保证所有线程,甚至自己都在递增 . 例如:计数器同时看到变量的同一面 . 它不是用来代替同步或原子或其他东西,它完全使读取同步 . 请不要将它与其他java关键字进行比较 . 如下面的示例所示,易失性变量操作也是原子的,它们会立即失败或成功 .

    package io.netty.example.telnet;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
    
        public static volatile  int a = 0;
        public static void main(String args[]) throws InterruptedException{
    
            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }
    
            for (Thread thread : list) {
                thread.start();
            }
    
            Thread.sleep(20000);
            System.out.println(a);
        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a++;
                System.out.println("a = "+Main.a);
            }
        }
    }
    

    即使你把挥发性或不结果总是不同的 . 但是如果您使用AtomicInteger,则结果将始终相同 . 这与同步也是一样的 .

    package io.netty.example.telnet;
    
        import java.util.ArrayList;
        import java.util.List;
        import java.util.concurrent.atomic.AtomicInteger;
    
        public class Main {
    
            public static volatile  AtomicInteger a = new AtomicInteger(0);
            public static void main(String args[]) throws InterruptedException{
    
                List<Thread> list = new  ArrayList<Thread>();
                for(int i = 0 ; i<11 ;i++){
                    list.add(new Pojo());
                }
    
                for (Thread thread : list) {
                    thread.start();
                }
    
                Thread.sleep(20000);
                System.out.println(a.get());
    
            }
        }
        class Pojo extends Thread{
            int a = 10001;
            public void run() {
                while(a-->0){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Main.a.incrementAndGet();
                    System.out.println("a = "+Main.a);
                }
            }
        }
    
  • 12

    是的,我使用它非常多 - 它对于多线程代码非常有用 . 你指出的文章很好 . 虽然有两件重要的事情要记住:

    • 如果你完全理解它的作用以及它与同步的不同之处,你应该只使用volatile . 在许多情况下,表面上看,volatile似乎是一个更简单,更高效的同步替代方案,但通常对volatile的更好理解会明确表明synchronized是唯一可行的选项 .

    • volatile现在没有找到它 . 如果您能够控制程序将在其上运行的JVM,请务必查看它 .

  • 0

    绝对没错 . (而且不只是在Java中,而且在C#中 . )有时你需要获取或设置一个值,保证在你的给定平台上是一个原子操作,例如int或boolean,但不要求线程锁定的开销 . volatile关键字允许您确保在读取获取当前值的值时,而不是通过在另一个线程上写入而过时的缓存值 .

  • 5

    访问volatile字段的每个线程将在继续之前读取其当前值,而不是(可能)使用缓存值 .

    只有成员变量可以是易失性的或瞬态的 .

  • 0

    volatile关键字有两种不同的用法 .

    • 阻止JVM从寄存器读取值(假设为缓存),并强制从内存中读取其值 .

    • 降低内存不一致性错误的风险 .

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

    忙标志用于防止线程在设备繁忙时继续,并且标志不受锁保护:

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

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

    busy = 0;
    

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

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

    使用volatile变量可以降低 memory consistency errors 的风险,因为对volatile变量的任何写操作都会与后续读取同一个变量 Build 关系.1806117_ . 这意味着对volatile变量的更改始终对其他线程可见 .

    没有内存一致性错误的读取,写入技术称为 atomic action .

    原子动作是一次有效发生的动作 . 原子动作不能在中间停止:它要么完全发生,要么根本不发生 . 在动作完成之前,原子动作的副作用是不可见的 .

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

    • 读取和写入对于引用变量和大多数原始变量(除long和double之外的所有类型)都是原子的 .

    • 对于声明为 volatile 的所有变量(包括长变量和双变量),读取和写入都是原子的 .

    干杯!

  • 11

    易失性变量是轻量级同步 . 当需要在所有线程中查看最新数据并且原子性可能受到损害时,在这种情况下,必须首选Volatile Variables . 读取volatile变量总是返回由任何线程完成的最近写入,因为它们既不缓存在寄存器中也不缓存在其他处理器看不到的缓存中 . 易失性是无锁的 . 当场景符合上面提到的标准时,我使用volatile .

  • 2

    来自oracle文档page,需要使用volatile变量来修复内存一致性问题:

    使用volatile变量可降低内存一致性错误的风险,因为对volatile变量的任何写入都会 Build 与之后读取同一变量的先发生关系 .

    这意味着对 volatile 变量的更改始终对其他线程可见 . 它还意味着当线程读取volatile变量时,它不仅会看到 volatile 的最新更改,还会看到导致更改的代码的副作用 .

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

    请查看jenkov教程页面以便更好地理解 .

    查看相关的SE问题,了解有关使用volatile的volatile和用例的更多详细信息:

    Difference between volatile and synchronized in Java

    一个实际用例:

    您有许多线程,需要以特定格式打印当前时间,例如: java.text.SimpleDateFormat("HH-mm-ss") . Yon可以有一个类,它将当前时间转换为 SimpleDateFormat 并每隔一秒更新一次变量 . 所有其他线程可以简单地使用此volatile变量在日志文件中打印当前时间 .

  • 647

    通过在Java应用程序中并发运行线程来异步修改 Volatile variable . 不允许变量的本地副本与"main" memory中当前保存的值不同 . 实际上,声明为volatile的变量必须使其数据在所有线程之间同步,这样无论何时在任何线程中访问或更新变量,所有其他线程都会立即看到相同的值 . 当然,易变量可能比"plain"变量具有更高的访问和更新开销,因为线程可以拥有自己的数据副本的原因是为了提高效率 .

    当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应该与其他内存操作重新排序 . 易失性变量不会缓存在寄存器或缓存中,而是隐藏在其他内存中处理器,因此读取volatile变量总是返回任何线程的最近写入 .

    供参考,请参考http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html

  • 0

    挥发性确实如下 .

    1>不同线程读取和写入volatile变量总是来自内存,而不是来自线程自己的缓存或cpu寄存器 . 所以每个线程总是处理最新的值 . 2>当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

  • 3

    我喜欢Jenkov's explanation

    Java volatile关键字用于将Java变量标记为“存储在主存储器中” . 更确切地说,这意味着,每次读取一个volatile变量都将从计算机的主内存中读取,而不是从CPU缓存中读取,并且每次写入volatile变量都将写入主内存,而不仅仅是CPU缓存 . 实际上,从Java 5开始,volatile关键字不仅仅保证将易失性变量写入主内存并从主内存中读取 .

    它是扩展的可视性保证,所谓的发生前保证 .

    volatile的性能考虑因素读取和写入volatile变量会导致变量被读取或写入主存储器 . 读取和写入主存储器更加昂贵而不是访问CPU缓存 . 访问volatile变量也会阻止指令重新排序,这是一种正常的性能增强技术 . 因此,当您确实需要强制实施变量可见性时,应该只使用volatile变量 .

相关问题