关于java:volatile关键字的用途是什么

What is the volatile keyword useful for

在今天的工作中,我遇到了Java中的EDCOX1×0关键字。由于不太熟悉,我发现了以下解释:

Java theory and practice: Managing volatility

考虑到这篇文章对所讨论的关键字的详细解释,您是否曾经使用过它,或者您是否曾经看到过这样一种情况:您可以以正确的方式使用这个关键字?


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

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

我非常推荐的"实践中的Java并发"一书,对EDCOX1的0个解释给出了一个很好的解释。这本书是由写IBM文章的同一个人写的(事实上,他在文章的底部引用了他的书)。我使用volatile是他的文章所称的"模式1状态标志"。

如果您想了解更多关于EDOCX1 0如何在引擎盖下工作,请阅读Java内存模型。如果您想超越这个水平,请阅读像Hennessy&Patterson这样的优秀计算机体系结构书籍,并阅读有关缓存一致性和缓存一致性的内容。


"…volatile修饰符保证任何读取字段的线程都能看到最近写入的值。"-josh bloch如果您想使用volatile,请阅读处理原子行为的java.util.concurrent包。维基百科上关于单件模式的文章显示了使用中的不稳定性。


关于volatile的要点:

  • Java中的同步可以通过使用Java关键字EDOCX1、1和EDCOX1、0和锁来实现。
  • 在Java中,我们不能拥有EDCOX1×1变量。在变量中使用synchronized关键字是非法的,会导致编译错误。而不是在爪哇中使用EDCOX1×1变量,可以使用Java EDCOX1×0变量,这将指示JVM线程从主内存读取EDCOX1×0变量的值,并且不在本地缓存它。
  • 如果一个变量不在多个线程之间共享,那么就不需要使用volatile关键字。
  • 来源

    volatile的示例用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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本身处理的,这样的更新将由所有读取器线程可见。

    Conclusion: volatile keyword is also used to communicate the content of memory between threads.

    不易挥发的示例用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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;  
        }

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

    enter image description here

    在Java中使用EDCOX1 0的用法:

    fail fast迭代器通常使用list对象上的volatile计数器来实现。

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

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


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

    不是你应该编写自己的线程,Java 1.6有很多漂亮的线程池。但如果你确定你需要一根线,你就需要知道如何阻止它。

    我用于线程的模式是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
      }
    }

    注意如何不需要同步


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


    volatile关键字声明的变量有两个主要特性,使其与众不同。

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

  • 如果有一个写操作正在对一个易失变量进行,并且突然请求一个读操作,则可以保证写操作将在读操作之前完成。

  • 以上两个品质可以推断

    • 所有读取可变变量的线程都将绝对读取最新的值。因为没有缓存值会污染它。而且,只有在当前写入操作完成后,才会授予读取请求。

    另一方面,

    • 如果我们进一步研究我提到的2,我们可以看到,volatile关键字是维护共享变量的理想方法,该共享变量具有n个读线程,只有一个写线程可以访问它。一旦我们添加了volatile关键字,就完成了。关于线程安全没有任何其他开销。

    反过来说,

    我们不能仅仅使用volatile关键字来满足一个共享变量,这个共享变量有多个写线程访问它。


    没有人提到对长双变量类型的读写操作的处理。读和写是引用变量和大多数基元变量的原子操作,除了长变量和双变量类型之外,它们必须使用volatile关键字进行原子操作。@链接


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


    在我看来,除了停止使用volatile关键字的线程外,还有两个重要的场景:

  • 双重检查锁止机构。常用于单件设计模式。在此情况下,需要将singleton对象声明为volatile。
  • 假醒。线程有时可能会从等待调用中唤醒,即使没有发出通知调用。这种行为称为supurious唤醒。这可以通过使用条件变量(布尔标志)来实现。只要标志为true,就将wait()调用放入while循环中。因此,如果线程由于除notify/notifyall之外的任何原因从wait调用中唤醒,那么它遇到的标志仍然为true,因此调用会再次等待。在调用notify之前,请将此标志设置为true。在这种情况下,布尔标志被声明为volatile。

  • 如果您正在开发一个多线程应用程序,您需要使用"volatile"关键字或"synchronized"以及任何其他您可以使用的并发控制工具和技术。这种应用程序的例子是桌面应用程序。

    如果您正在开发一个将部署到应用服务器(Tomcat、JBoss AS、Glassfish等)的应用程序,则不必像应用服务器已经解决的那样处理并发控制。事实上,如果我正确记得JavaEE标准禁止servlet和EJB中的任何并发控制,因为它是"基础设施"层的一部分,您应该释放它。只有在实现singleton对象的情况下,才能在此类应用程序中执行并发控制。如果您使用框架结构(如Spring)编织组件,甚至已经解决了这一问题。

    因此,在大多数应用程序是Web应用程序和使用IOC框架(如Spring或EJB)的Java开发中,不需要使用"易失性"。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    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,结果将始终相同。这同样适用于Synchronized。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
        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);
                }
            }
        }

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

  • 只有当你完全理解它的作用以及它与同步的区别。在许多情况下,易失性出现,表面上,更简单性能替代同步,通常情况下更好了解挥发性物质会使很明显,只有同步可行的选择。
  • volatile在尽管如此,许多老的合资企业同步完成。我记得看到一个文档引用了不同JVM中不同级别的支持,但不幸的是我现在找不到它。如果你使用Java Prime1.5,或者如果你没有控制程序运行的JVM,那么一定要查看它。

  • 当然,是的。(不仅在Java中,而且在C语言中),有时需要获取或设置保证在给定平台上的原子操作的值,例如int或布尔值,但不需要线程锁定的开销。volatile关键字允许您确保在读取当前值的值时,而不是刚被另一线程上的写入操作废弃的缓存值。


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

    只有成员变量可以是可变的或瞬时的。


    挥发性变量是光加权同步。如果所有线程中最新数据的可见度都是需要的,而且原子性可能受到损害,那么在这种不稳定变量的情况下,必须预先确定数据的可见性。读取关于挥发性变量的最新文献,因为这些变量在寄存器或在其他处理器看不到的藏匿处都需要隐藏。挥发性是锁着的。我使用的是挥发性的,当剧本遇到前说过的标准。


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

  • 防止JVM从寄存器中读取值(假定为缓存),并强制从内存中读取其值。
  • 减少一致性错误中内存的风险。
  • Prevents JVM from reading values in register, and forces its
    value to be read from memory.

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

    1
    2
    3
    while (busy) {
        /* do something else */
    }

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

    1
    busy = 0;

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

    Reduces the risk of memory consistency errors.

    使用volatile变量可以降低内存一致性错误的风险,因为对volatile变量的任何写入都会建立"发生在"与同一变量的后续读取之间的关系。这意味着对可变变量的更改对于其他线程总是可见的。

    无内存一致性错误的读、写技术称为原子作用。

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

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

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

    干杯!


    挥发物会跟随。

    1>不同线程对可变变量的读写总是来自内存,而不是来自线程自己的缓存或CPU寄存器。所以每个线程总是处理最新的值。2>当两个不同的线程在堆中使用相同的实例或静态变量时,一个线程可能会认为另一个线程的操作有问题。参见杰里米·曼森的博客。但易变有帮助。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    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_订购


    From Oracle documentation page,the need for volatile variable arises to fix memory consistency issues:

    Using volatile variables reduces the risk of memory consistency errors, because any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable.

    这意味着变化为volatile的变量总是可见于其他威胁。它还意味着当一个螺纹读取一个变量的变量时,它看起来不仅仅是volatile的最新变化,而且也是代码的一侧效应,代码指示了变化。

    在没有volatile的情况下,如在Peter Parker中解释,修改后的每一个螺纹堆栈可能都有自己的变量拷贝。通过使变量成为volatile,记忆的一致性问题已经被固定。

    请看Jenkov教学页,以便更好地理解。

    注意到一些关于挥发性&使用情况的相关问题:

    爪哇挥发性与同步性的差异

    One practical use case:

    你有许多线索,需要以特定格式打印当前时间:java.text.SimpleDateFormat("HH-mm-ss")。我们可以有一个等级,它将当前的时间转换为SimpleDateFormat,并为每一秒钟更新变量。所有其他线程都可以简单地使用这一挥发性变量,在记录文件中打印当前时间。


    volatile变量在主共享缓存行更新后,基本上用于即时更新(flush),以便立即将更改反映到所有工作线程。


    当使用一个变量时,挥发性键将确保通过阅读这个变量,可以看到同样的价值。如果你有多个线程阅读和写入一个变量,那么变量的挥发性就不会足够,数据会被腐蚀。图像威胁阅读了同样的价值,但每一个人都有过一些失误(表示增加了一个计数器),当写入回忆时,数据完整性被侵犯。这就是为什么需要实现不同的同步(不同的方式是可能的)

    如果变化是由1个螺纹发生的,而其他人只需要阅读这个值,则挥发性会是合适的。


    我喜欢Jenkov的解释:

    The Java volatile keyword is used to mark a Java variable as"being stored in main memory". More precisely that means, that every read of a volatile variable will be read from the computer's main memory, and not from the CPU cache, and that every write to a volatile variable will be written to main memory, and not just to the CPU cache.

    Actually, since Java 5 the volatile keyword guarantees more than just that
    volatile variables are written to and read from main memory.

    它是一种扩展的可见性保证,所谓的保证发生在保证之前。

    Performance Considerations of volatile

    Reading and writing of volatile variables causes the variable to be read or written to main memory. Reading from and writing to main memory is more expensive than accessing the CPU cache. Accessing volatile variables also prevent instruction reordering which is a normal performance enhancement technique. Thus, you should only use volatile variables when you really need to enforce visibility of variables.


    通过在Java应用程序中同时运行线程来异步修改易失性变量。不允许有与"主"内存中当前保存的值不同的变量的本地副本。实际上,声明为volatile的变量必须使其数据在所有线程之间同步,以便每当访问或更新任何线程中的变量时,所有其他线程都会立即看到相同的值。当然,与"普通"变量相比,易失性变量的访问和更新开销可能更高,因为线程可以拥有自己的数据副本是为了提高效率。

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

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


    下面是一个非常简单的代码,用于演示volatile对变量的要求,该变量用于控制来自其他线程的线程执行(这是需要volatile的一种情况)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // 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时:即使在'stopped on:xxx'之后,您也不会看到'stopped on:xxx'消息,并且程序继续运行。

    1
    Stopping on: 1895303906650500

    volatile使用时:您会立即看到'stopped on:xxx'。

    1
    2
    Stopping on: 1895285647980000
    324565439> Stopped on: 1895285648087300

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