在今天的工作,我碰到的 volatile 关键词。不是很熟悉它,我发现这样的解释:

Java理论和实践:管理波动

鉴于详细说明在该文解释的关键问题,你曾经使用它,或者可能你有没有看到一个情况中,你可以使用这个关键字的正确方式?

有帮助吗?

解决方案

volatile 有语义存储器的可见性。基本上,值 volatile 场变得可见的所有读者(其他线在特定的)之后,写操作的完成。没有 volatile, 读者可以看到一些非更新值。

来回答你的问题:是的,我用一个 volatile 变量控制是否有一些代码继续循环。循环试验 volatile 值,并继续如果它是 true.条件可以被设定为 false 通过调用"停止"方法。环看到 false 并且终止时的测试值之后停止的方法完成的执行。

这本书"Java并发在实践"我的高度推荐,提供了一个很好的解释 volatile.这本书写的是同一个人是谁写的IBM的文章中引用的问题(事实上,他援引了他的书底部的文章)。我的使用 volatile 为什么他的文章中呼吁的"模式1的状态标志。"

如果你想要了解更多有关如何 volatile 工发动机罩下,读了 Java存储器模型.如果你想超越这一水平,检查了一个良好的计算机建筑书喜欢 轩尼诗&帕特森 和阅读有关缓协调一致和高速缓存的一致性。

其他提示

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

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

关于 Singleton Pattern 的维基百科帖子显示使用不稳定。

重要的一点关于 volatile:

  1. 同步Java能够通过使用关键词 synchronizedvolatile 和锁。
  2. 在爪哇,我们可以不必 synchronized 变量。使用 synchronized 关键字的变量是非法的,并将导致汇编的错误。而不是使用 synchronized 变量,可以使用java volatile 变量,它将指导JVM线阅读的价值 volatile 变,从主存储器和不高速缓存这在本地。
  3. 如果一个变量之间不共享多线程的那么就没有必要使用 volatile 关键词。

来源

例的使用情况 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 是不是能够进行通信的其他线。因此,如果螺的一个创建单一实例只是在创建之后,CPU破坏等等,所有其他线程将不能够看到价值的 _instance 因为没有空和他们会相信它仍然是分配的空。

为什么这种事发生?因为阅读器的线是不这样做的任何锁,直到作家线来到了一个同步块,记忆会不会同步和价值的 _instance 将不会更新在主存储器。与挥发性的关键词,这是处理通过Java本身与这种更新将是可见的由所有读者的螺纹。

结论: 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;  
    }

代码以上是无线的安全。虽然它检查的价值的实例再次内同步块(由于性能的原因),JIT编译器可以重新排列码的方式,参照实例之前设置的构造已经完成了它的执行。这意味着方法getInstance()返回的对象,不可能已经被初始化。使代码线的安全,关键词挥发可以用于Java5的实例可变的。变量的标记为挥发性只获得可见于其他线一旦构造的目已经完成了其执行完全。
来源

enter image description here

volatile 使用Java:

快速失败是迭代 通常 实现用一个 volatile 反名单上的对象。

  • 当该清单被更新,反增加。
  • 当一个 Iterator 创建,目前价值计数是嵌入 Iterator 对象。
  • 当一个 Iterator 操作执行,方法比较这两个计数器的价值观并引发 ConcurrentModificationException 如果他们是不同的。

执行失败的安全的迭代通常是重量轻。它们一般依靠性能的特定执行情况的数据结构。没有普遍模式。

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

注意不需要同步

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

一个可变的声明 volatile 关键字,有两个主要的素质,使它特别的。

  1. 如果我们有一种挥发性可变的,也不能缓存入计算机(微处理器)高速缓冲存储器由任何线。访问总是发生了从主存储器。

  2. 如果有一个 写作 去挥发性变量,突然一个 读操作 被请求,它保证 写作将会完成之前读的操作.

两个推断,上述特质

  • 所有的线阅读挥发性变将肯定读过的最新的价值。因为没有缓存的价值可以污染。也读请求将被授予之后,才完成当前的写作。

而另一方面,

  • 如果我们进一步调查 #2 我已经提到的,我们可以看到, volatile 关键词是一个理想的方式保持一个共同变量,它已 'n'数的读线和只有一个写入线 访问。一旦我们加入 volatile 关键词,这样做。没有任何其他开销约线的安全。

Conversly,

我们 不能 使用 volatile 关键词仅仅满足一个共同变量,它已 超过一个写线访问它.

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

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

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

  1. 双重检查锁定机制。经常用于Singleton设计 图案。在此,单例对象需要声明为volatile
  2. 虚假唤醒。即使没有发出通知呼叫,线程有时也会从等待呼叫中唤醒。这种行为称为supurious wakeup。这可以通过使用条件变量(布尔标志)来抵消。只要标志为真,就将wait()调用放入while循环中。因此,如果由于除了notify / notifyall之外的任何原因线程从等待调用中唤醒,那么它遇到标志仍然是真的,因此再次调用wait。在调用notify之前,将此标志设置为true。在这种情况下,布尔标志被声明为volatile

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

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

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

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

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

  1. 你应该只使用volatile 完全明白它的作用 以及它与同步的不同之处。 在许多情况下出现挥发性, 从表面上看,更简单一点 高效的替代品 同步,往往更好 理解挥发性会使 明确同步是唯一的 可行的选项。
  2. volatile实际上并不适用于 但是,很多较旧的JVM 同步了。我记得看到一个文档引用了不同JVM中的各种级别的支持,但遗憾的是我现在找不到它。如果您正在使用Java pre 1.5,或者如果您无法控制运行程序的JVM,请务必查看它。

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

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

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

有两种不同的使用挥发性的关键字。

  1. 防止JVM从阅读值从登记册(假设为缓),并迫使它的价值可以从中读取存储器。
  2. 降低风险的存在的一致性错误。

防止JVM从阅读值登记,其部队 值可以从中读取存储器。

一个 繁忙的标志 是用来防止一个线程的持续,而该设备是繁忙和标记没有保护通过一个锁定:

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

测试线将会继续在另外一个线程关闭 繁忙的标志:

busy = 0;

然而,由于繁忙的访问经常在线测试,JVM可能优化的考试通过把价值的繁忙的一个登记册,然后测试的内容登记册没有阅读的价值在繁忙的存储器之前,每一个测试。测试线绝不会看到繁忙的变化和其他线只会改变价值的繁忙的存在,导致僵局。宣布 繁忙的标志 如挥发性的部队,其价值可以读之前,每一个测试。

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

使用挥发性变量减少的风险 存储器的一致性错误。, 因为任何写信给挥发性变量建立一个 "发生之前" 关系后续读的同一变量。这意味着改变一种挥发性变总是看到其他的螺纹。

该技术的阅读、写作没有记忆的一致性错误是所谓 原子行动.

一个原子的行动是一种有效地发生的一次。一个原子的行动不能停止在中东:它无论发生完全的,或它不会发生在所有。没有副作用的一个原子的行动是可见的,直到该行动完成。

下面的行动可以指定,原子:

  • 读和写的都是原子参考的变量,对于大多数 原始变量(所有类型除外长和双).
  • 读和写的都是原子的所有声明的变量 易失性 (包括长和双变量)。

干杯!

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

挥发性确实跟随。

1&GT;不同线程对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

从oracle文档页面,对volatile的需求变量出现以修复内存一致性问题:

  

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

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

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

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

查看相关的SE问题,了解有关volatile&amp; amp ;;的详细信息。用例使用volatile:

Java中的volatile和synchronized之间的区别

一个实际用例:

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

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

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

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

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

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

我喜欢 Jenkov的解释

  

Java volatile 关键字用于将Java变量标记为“存储在主存储器中”。更准确地说,这意味着,每次读取一个volatile变量都将从计算机的主内存中读取,而不是从CPU缓存读取,并且每次写入一个volatile变量都将写入主内存,而不仅仅是CPU缓存

     

实际上,自Java 5起,volatile关键字不仅仅保证了这一点   volatile变量被写入主存储器并从主存储器读取。

这是扩展的可见性保证所谓的事先保证。

  

易失性

的性能考虑因素      

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

volatile变量在更新后主要用于主共享缓存行中的即时更新(刷新),以便更改立即反映到所有工作线程。

下面是一个非常简单的代码,用于演示 volatile 对变量的要求,该变量用于控制来自其他线程的线程执行(这是 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 未使用时:即使在'停止后,您也永远不会看到'已停止:xxx '消息:xxx ',程序继续运行。

Stopping on: 1895303906650500

使用 volatile :您会立即看到“已停止:xxx ”。

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top