今天的工作中,我遇到了Java中的volatile关键字。 不太熟悉,我发现了以下解释:

Java理论与实践:管理波动

鉴于该文章详细解释了所讨论的关键字,您是否曾经使用过它,或者是否曾见过可以正确使用该关键字的情况?


#1楼

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

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

Wikipedia上有关Singleton Pattern的帖子显示使用情况不稳定。


#2楼

绝对没错。 (不仅在Java中,而且在C#中也是如此。)有时,您需要获取或设置一个值,该值必须保证是给定平台上的原子操作,例如int或boolean,但不需要线程锁定的开销。 volatile关键字使您可以确保在读取值时获得的是当前值,而不是刚被另一个线程的写入废弃的缓存值。


#3楼

volatile具有内存可见volatile语义。 基本上,对所有读取器(尤其是其他线程)而言, volatile字段的值在其上完成写操作之后变为可见。 没有volatile ,读者可以看到一些未更新的值。

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

我极力推荐的“ 实践中的Java并发性 ”一书很好地解释了volatile 。 这本书是由撰写该问题中引用的IBM文章的同一人撰写的(实际上,他在该书的底部引用了他的书)。 我对volatile使用是他的文章所说的“模式1状态标志”。

如果您想了解有关volatile在幕后如何工作的更多信息,请阅读Java内存模型 。 如果您想超越该水平,请阅读Hennessy&Patterson这样的优秀计算机体系结构书籍,并阅读有关缓存一致性和缓存一致性的信息。


#4楼

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


#5楼

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


#6楼

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

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

因此,在大多数Java开发中,其中应用程序是Web应用程序,并使用IoC框架(如Spring或EJB),则不需要使用“ volatile”。


#7楼

是的,我经常使用它-它对多线程代码非常有用。 您所指的文章是一篇不错的文章。 尽管有两点要牢记:

  1. 仅当您完全了解volatile以及它与同步有何不同时,才应使用volatile。 在许多情况下,表面上看起来易失性是同步的更简单,更高性能的选择,而通常人们对易失性的更好理解会清楚地表明,同步是唯一可行的选择。
  2. volatile实际上在许多旧版JVM中均不起作用,尽管已同步。 我记得看到过一个文档,其中引用了不同JVM中的各种支持级别,但不幸的是我现在找不到。 如果您使用的是Java 1.5之前的版本,或者您无法控制将要运行程序的JVM,则一定要仔细研究一下。

#8楼

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

请注意,无需同步


#9楼

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

#10楼

我认为,除了停止使用volatile关键字的线程外,还有两个重要的方案:

  1. 双重检查锁定机制 。 通常用于Singleton设计模式。 在这种情况下, 需要单例对象声明为volatile
  2. 虚假的唤醒 。 即使没有发出通知调用,线程有时也可能从等待调用中唤醒。 此行为称为伪唤醒。 可以通过使用条件变量(布尔标志)来解决。 只要标志为真,就将wait()调用放入while循环中。 因此,如果线程由于除Notify / NotifyAll之外的任何其他原因而从等待调用中唤醒,则它遇到的标志仍然为true,因此调用再次等待。 在调用notify之前,将此标志设置为true。 在这种情况下, 布尔标志被声明为volatile

#11楼

每个访问易失性字段的线程将在继续之前读取其当前值,而不是(潜在地)使用缓存的值。

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


#12楼

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


#13楼

关于volatile要点:

  1. 通过使用Java关键字synchronizedvolatile和locks,可以在Java中进行synchronized
  2. 在Java中,不能有synchronized变量。 将synchronized关键字与变量一起使用是非法的,并且会导致编译错误。 可以在Java中使用volatile变量,而不是在Java中使用synchronized变量,该变量将指示JVM线程从主内存读取volatile变量的值,而不是在本地对其进行缓存。
  3. 如果没有在多个线程之间共享变量,则无需使用volatile关键字。

资源

volatile用法示例:

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

在第一个请求到达时,我们正在懒惰地创建实例。

如果我们不使_instance变量volatile那么创建Singleton实例的Singleton将无法与其他线程通信。 因此,如果线程A正在创建Singleton实例,并且在创建后,CPU损坏等情况下,所有其他线程将无法看到_instance的值不为null,他们将认为该实例仍被分配为null。

为什么会这样? 因为读取器线程未进行任何锁定,并且直到写入器线程从同步块中退出,否则内存将不同步,并且_instance值也不会在主内存中更新。 使用Java中的Volatile关键字,此关键字由Java本身处理,并且此类更新将在所有阅读器线程中可见。

结论volatile关键字还用于在线程之间传递内存的内容。

无挥发物的使用示例:

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

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

Java中的volatile用法

故障快速迭代器通常使用列表对象上的volatile计数器实现。

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

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


#14楼

volatile键与变量一起使用时,将确保读取此变量的线程将看到相同的值。 现在,如果您有多个线程读取和写入变量,则使变量volatile不够,数据将被破坏。 映像线程读取了相同的值,但是每个线程都做了一些修改(例如增加了一个计数器),当写回内存时,数据完整性受到侵犯。 这就是为什么有必要使变量同步(可能有不同的方式)的原因

如果更改是通过1个线程完成的,而其他线程仅需要读取此值,则volatile将是合适的。


#15楼

从oracle文档页面 ,需要volatile变量来解决内存一致性问题:

使用易失性变量可降低内存一致性错误的风险,因为对易失性变量的任何写操作都会与该变量的后续读取建立先发生后关系。

这意味着对volatile变量的更改始终对其他线程可见。 这也意味着,当一个线程读取volatile变量,它看到不仅仅是最新变化的volatile ,而且代码的副作用,导致了变化。

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

请参阅jenkov教程页面,以更好地理解。

请查看相关的SE问题,以获取有关volatile和使用volatile的用例的更多详细信息:

Java中的volatile和Synchronized之间的区别

一个实际的用例:

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


#16楼

易变变量是轻量级同步。 如果要求所有线程之间都具有最新数据可见性,并且原子性可能会受到影响,则在这种情况下,必须首选易变变量。 对易失性变量的读取总是返回任何线程进行的最新写入,因为它们既不缓存在寄存器中,也不缓存在其他处理器看不到的缓存中。 挥发物是无锁的。 当方案满足上述条件时,我将使用volatile。


#17楼

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

  1. 防止JVM从寄存器中读取值(假定为缓存),并强制从内存中读取其值。
  2. 降低内存不一致错误的风险。

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

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

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

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

busy = 0;

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

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

使用易失性变量可降低内存一致性错误的风险,因为对易失性变量的任何写入都会与该变量的后续读取建立“先发生”关系。 这意味着对volatile变量的更改始终对其他线程可见。

没有内存一致性错误的读写技术称为原子动作

原子动作是一次有效地同时发生的动作。 原子动作不能停在中间:它要么完全发生,要么根本不发生。 直到动作完成,原子动作的副作用才可见。

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

  • 对于参考变量和大多数原始变量(除long和double以外的所有类型),读写都是原子的。
  • 对于声明为volatile的所有变量(包括long和double变量),读写都是原子的。

干杯!


#18楼

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

  1. 如果我们有一个volatile变量,则任何线程都无法将其缓存到计算机的(微处理器)缓存中。 访问总是从主存储器发生的。

  2. 如果对易失变量执行写操作 ,并且突然请求读操作 ,则可以保证写操作将在读操作之前完成

以上两个素质可以推论出

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

另一方面,

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

相反,

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


#19楼

易失性确实在跟随。

1>不同线程对易失性变量的读写始终来自内存,而不是线程自己的缓存或cpu寄存器。 因此,每个线程始终处理最新值。 2>当2个不同的线程在堆中使用相同的实例或静态变量时,一个线程可能会看到其他线程的操作混乱。 参见jeremy manson的博客。 但是,挥发性在这里有帮助。

下面的代码完全运行,显示了许多线程如何可以按预定义的顺序执行并打印输出,而无需使用synced关键字。

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 stubThread[] 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;}@Overridepublic void run() {// TODO Auto-generated method stubwhile (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


#20楼

下面是一个非常简单的代码,用于演示变量对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 threadSystem.out.println("Stopping on: " + stoppedOn);}
}class Task implements Runnable {// Try running with and without 'volatile' hereprivate volatile boolean state = true;private int i = 0;public void stop() {state = false;} @Overridepublic void run() {while(state) {i++;}System.out.println(i + "> Stopped on: " + System.nanoTime());}
}

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

Stopping on: 1895303906650500

当使用volatile您将立即看到' Stopped on:xxx '。

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

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


#21楼

volatile变量基本上在更新后基本上用于主共享缓存行中的即时更新(刷新),因此更改会立即反映到所有工作线程中。


#22楼

它是管理硬件内存管理的一部分。 实际上,属性的值至少可以存储在RAM memoryCPU cache memory

在多线程环境中,我们有一个shared resources的概念,这是该领域的问题之一。 理论上,当您使用过时的资源时可能会遇到这种情况,因为不同的处理器使用不同的值。

Java具有保留volatile关键字来应对这种情况。 它用于标记Java变量以直接和原子方式读写RAM内存

Java 5扩展了称为责任happens-before guarantee volatile责任[About]happens-before每次后续读取同一字段happens-before都会对volatile字段进行写操作。

但是,当几个线程同时在volatile字段中写入不同的值(即race condition )时,情况又如何呢? synchronized关键字是为了拯救[关于]

什么时候挥发足够?

当仅一个线程写入而其他线程仅读取volatile保证时,它们将看到最新值

还请考虑使用volatile变量是更昂贵的任务,它还可以防止指令重新排序(即使用技术happens-before ),这是正常的性能增强技术。 因此,仅在真正需要时才使用volatile变量。

阅读詹科夫的解释

volatile关键字对相关推荐

  1. java中实现具有传递性吗_Java中volatile关键字详解,jvm内存模型,原子性、可见性、有序性...

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  2. volatile关键字之全面深度剖析

    引言 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字 ...

  3. c语言中volatile关键字的作用

    读文章之前 可以先看一下<程序员的自我修养 >第28页 过度优化. volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直 ...

  4. C语言volatile关键字详解

    volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据.如果没有volatile关键字,则编译器可能优化读取和存储 ...

  5. 爆赞,对 volatile 关键字讲解最好的一篇文章!

    欢迎关注方志朋的博客,回复"666"获面试宝典 最近,在一篇文章中了解到了 volatile 关键字,在强烈的求知欲趋使下,我查阅了一些相关资料进行了学习,并将学习笔记记录如下,希 ...

  6. Java并发编程:JMM和volatile关键字

    Java内存模型 随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内置高速缓存区.高速缓存区的加入使得CP ...

  7. c#中volatile关键字的作用

    恐怕比较一下volatile和synchronized的不同是最容易解释清楚的.volatile是变量修饰符,而synchronized则作用于一段代码或方法:看如下三句get代码: int i1;  ...

  8. 26张图带你彻底搞懂volatile关键字

    引子 小艾吃饭路上碰上小牛,忙问:你昨天面大厂面的咋样了?听说他们最喜欢问多线程相关知识. 小牛说:对啊,第一个问题我就讲了20分钟,直接把面试官讲服了. 小艾忙问:什么问题能讲这么久?是不是问你情感 ...

  9. C++中的volatile关键字

    1.使用 volatile int i = 10; 2.使用volatile时要几个注意的点: volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统.硬 ...

  10. volatile关键字的作用、原理

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到"半个"单例. 而发挥神奇作用的volatile,可以当之 ...

最新文章

  1. 死磕Java并发:J.U.C之Java并发容器:ConcurrentLinkedQueue
  2. Myeclipse 10 使用之修改字体
  3. springboot mail插件/JavaMailSenderImpl 发邮件。163网易企业邮箱调用的坑。
  4. 【机器视觉】 dev_update_window算子
  5. iframe打印excel bold_搭载君正X1000E芯片 中盈SP7080激光打印机现已开售!
  6. winddows 运行指令 (2)
  7. Oracle 20c 新特性:基础级内存数据库免费功能 In-Memory Base Level
  8. Java多线程间的数据共享
  9. 《人工智能及其应用》1-6章
  10. visio阵列_什么软件可以画这种图,Visio怎么画?
  11. 计算机抓取整个屏幕的按键,技术员搞定电脑截全屏快捷键【搞定方法】
  12. linux yum远程安装软件,Linux 自己制作yum源和实现远程yum安装
  13. Linux查看网卡是千兆还是万兆网卡
  14. VUE调用打印机打印页面
  15. 给学习iOS开发同学的建议
  16. 我的Windows实用软件清单
  17. 几组数据的相关性python_几的笔顺 笔画数:2 拼音:jī,jǐ 部首:几 - 智慧山
  18. 【生信笔记】python实现DNA反向互补序列的6种方法
  19. 小说形象特征包括哪些方面_小说的基本特征是塑造人物形象
  20. myBatis之update操作的注意事项

热门文章

  1. Android AOSP 单独编译某一模块
  2. Android onConfigurationChanged 收不到回调
  3. 【Android】安卓布局文件中xmlns属性
  4. java.lang.AbstractMethodError: com.mysql.jdbc.PreparedStatement.setCharacterStream(ILjava/io/Reader;
  5. 第十六周程序阅读(2)
  6. springboot处理参数再转发请求_SpringBoot图文教程6—SpringBoot中过滤器的使用
  7. Android之SimpleAdapter使用
  8. python数据分析numpy_Python数据分析之numpy学习
  9. PageAdmin CMS网站建设教程:如何实现信息的定时发布
  10. 如何写好一篇高质量的IEEE/ACM Transaction级别的计算机科学论文?