你有没有在Java中使用volatile关键字?
在今天的工作中,我遇到了Java中的volatile
关键字。 不是很熟悉,我find了这个解释:
Java理论与实践:pipe理波动性
鉴于该文章解释关键字的详细信息,您是否曾经使用它,或者您是否曾经看到可以正确使用此关键字的情况?
volatile
具有内存可见volatile
语义。 基本上,写操作完成后,所有读写器(特别是其他线程)都可以看到volatile
字段的值。 没有volatile
,读者可以看到一些未更新的价值。
回答你的问题:是的,我使用一个volatile
variables来控制是否有代码继续循环。 循环testingvolatile
值,如果为true
继续。 通过调用“stop”方法可以将条件设置为false
。 循环会看到false
并在stop方法完成执行后testing值时终止。
我强烈推荐的书“ Java Concurrency in Practice ”给出了volatile
一个很好的解释。 这本书是由编写问题中引用的IBM文章的同一个人撰写的(实际上,他在这篇文章的底部引用了他的书)。 我的volatile
使用是他的文章称为“模式1状态标志”。
如果你想了解更多关于如何volatile
工作,请阅读Java内存模型 。 如果你想超越这个层次,请查看像Hennessy&Patterson这样的好计算机体系结构书,并阅读关于caching一致性和caching一致性的内容。
“… volatile修饰符保证读取字段的任何线程都能看到最近写入的值。” – Josh Bloch
如果您正在考虑使用volatile
,请阅读处理primefaces行为的java.util.concurrent
包。
单例模式的维基百科发布显示使用中不稳定。
关于易失性的重点:
- Java中的同步可以通过使用Java关键字
synchronized
和volatile
。 - 在Java中,我们不能有
synchronized
variables。 在variables中使用synchronized
关键字是非法的,将导致编译错误。 您可以使用Javavolatile
variables,而不是Java中的synchronized
variables,它将指示JVM线程从主内存中读取volatile
variables的值,而不是在本地caching它。 - 如果多个线程之间不共享一个variables,则不需要在该variables中使用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; } }
我们在第一个请求来的时候懒洋洋地创build实例。
如果我们不使_instance
variables比创buildSingleton
实例的Thread不能传递其他线程,则创build该实例直到它出来Singleton块,所以如果线程A正在创buildSingleton实例并且只是在创build丢失CPU之后,所有其他线程将不能将_instance
值看作非null,并且它们将认为它仍然为空。
为什么? 因为读线程线程没有进行任何locking,直到写线程退出同步块,内存将不会同步, _instance
值将不会在主内存中更新。 在Java中使用Volatile关键字时,这是由Java自己处理的,所有读者线程都可以看到这些更新。
结论 :
volatile
关键字也用于在线程之间传递内存的内容。
不使用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; }
上面的代码不是线程安全的。 尽pipe它在同步块中再次检查实例的值(出于性能方面的原因),JIT编译器可以重新排列字节码,以便在构造函数完成执行之前设置对实例的引用。 这意味着方法getInstance()返回一个可能还没有完全初始化的对象。 为了让代码是线程安全的,关键字volatile可以在Java 5以后用于实例variables。 被标记为volatile的variables只有在对象的构造函数完成完成后才会被其他线程看到。
资源
java中的易失性使用失效快速迭代器通常使用列表对象上的volatile
计数器来实现。
- 列表更新时,计数器递增。
- 当一个
Iterator
被创build时,计数器的当前值被embedded在Iterator
对象中。 - 当执行
Iterator
操作时,该方法比较两个计数器值,如果它们不同则抛出ConcurrentModificationException
exception。
故障安全迭代器的实现通常是轻量级的。 它们通常依赖于特定列表实现的数据结构的属性。 没有一般的模式。
什么时候足够volatile
?
如果两个线程都读写共享variables,那么使用volatile
关键字是不够的。 在这种情况下,您需要使用同步来保证variables的读写是primefaces的。
但是如果一个线程读取和写入一个volatile
variables的值,而其他线程只读取该variables,那么读取线程就会保证看到写入volatile
variables的最新值。 没有使variablesvolatile
,这不能得到保证。
使用volatile
性能考虑因素:
读取和写入volatile
variables会导致variables被读取或写入主内存。 从主内存读取和写入比访问CPUcaching更加昂贵。 访问volatile
variables还可以防止指令重新sorting,这是一种正常的性能增强技术。 因此,如果您确实需要强制执行variables的可见性,则只能使用volatile
variables。
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
variables作为标志来终止一个线程。 如果你已经启动了一个线程,并且你希望能够安全的从另一个线程中断它,你可以让线程定期检查一个标志。 要停止它,将标志设置为true。 通过将标志设置为volatile
,可以确保正在检查的线程在下次检查时会看到它已经被设置,而无需使用synchronized
块。
没有人提到过长双variablestypes的读写操作。 读写操作是对引用variables和大多数基本variables的primefaces操作,除了long和doublevariablestypes,必须使用volatile关键字作为primefaces操作。 @链接
是的,只要你想要一个可变variables被多个线程访问,就必须使用volatile。 这不是很常见的用例,因为通常你需要执行多个primefaces操作(例如,在修改它之前检查variables的状态),在这种情况下,你会使用一个同步块。
国际海事组织除了停止使用volatile关键字的线程两个重要的情况是
- 双重检查locking机制 。 经常使用Singletondevise模式。 在这个
singleton object needs to be declared volatile
。 - 虚假的唤醒 。 即使没有发出通知呼叫,线程有时也可能会从等待中唤醒。 这种行为称为supurious唤醒。 这可以通过使用条件variables(布尔标志)来抵消。 只要标志为true,就把wait()调用放在while循环中。 所以如果由于除了notify / notifyall以外的任何原因,线程从等待中唤醒,那么它遇到的标志仍然是真的,因此调用再次等待。 在调用notify之前,将此标志设置为true。 在这种情况下,
boolean flag is declared as volatile
。
如果您正在开发multithreading应用程序,则需要使用'volatile'关键字或'synchronized'以及任何其他并发控制工具和技术。 这种应用的例子是桌面应用程序。
如果您正在开发将部署到应用程序服务器(Tomcat,JBoss AS,Glassfish等)的应用程序,则不必像应用程序服务器已经解决的那样处理并发控制。 事实上,如果我记住了正确的话,那么Java EE标准将禁止servlet和EJB中的任何并发控制,因为它是“基础结构”层的一部分,您应该从中免除它的处理。 如果你正在实现单例对象,你只能在这样的应用程序中进行并发控制。 这甚至已经解决,如果你使用像Spring一样的框架编织你的组件。
因此,在大多数应用程序是Web应用程序并使用IoC框架(如Spring或EJB)的Java开发中,您不需要使用“volatile”。
只有volatile
才能保证所有的线程,甚至是自己,都在递增。 例如:一个计数器同时看到variables的同一个面。 它不是用来代替同步或primefaces或其他的东西,它完全使读取同步。 请不要将它与其他java关键字进行比较。 如下面的例子所示,volatilevariables操作也是primefaces的,它们一次失败或成功。
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); } } }
是的,我使用它很多 – 它可以是非常有用的multithreading代码。 你指出的文章是一个很好的文章。 虽然有两件重要的事情要记住:
- 如果你完全理解它的作用,以及它与同步的不同,你应该只使用volatile。 在很多情况下,volatile表面上看起来是一个更简单,更高效的同步替代scheme,但是如果经常对volatile进行更好的理解,那么就会发现同步是唯一可行的select。
- volatile虽然是同步的,但实际上在很多较旧的JVM中并不起作用。 我记得看到一个引用了不同JVM中不同级别支持的文档,但不幸的是我现在找不到它。 如果您使用的是Java 1.5以前的版本,或者如果您无法控制将运行程序的JVM,那么绝对可以考虑一下。
绝对没错。 (不仅仅是在Java中,而且在C#中。)有时候,你需要获取或者设置一个保证在给定的平台上是一个primefaces操作的值,例如int或者boolean,但是不要求线程locking的开销。 volatile关键字允许您确保在读取获取当前值的值时,而不是通过在另一个线程上写入而使其过时的caching值。
访问一个volatile字段的每个线程将在继续之前读取它的当前值,而不是(可能)使用一个caching值。
只有成员variables可以是易失性或瞬态的。
与variables一起使用时,volatile键将确保读取此variables的线程将看到相同的值。 现在,如果你有多个线程读写一个variables,使variablesvolatile变得不够,数据将被破坏。 图像线程已经读取相同的值,但是每一个都做了一些chage(比如增加一个计数器),当回写到内存时,数据完整性被侵犯了。 这就是为什么有必要使variables同步(不同的方式是可能的)
如果更改是由1个线程完成的,其他的只需要读取这个值,那么volatile将是合适的。
从oracle文档页面 ,需要volatilevariables来解决内存一致性问题:
使用volatilevariables可以降低内存一致性错误的风险,因为任何对volatilevariables的写操作都会build立一个before-before关系,并且随后读取同一个variables。
这意味着对其他线程总是可见的变化。 这也意味着当一个线程读取一个volatilevariables时,它不仅会看到volatile的最新变化,还会看到导致变化的代码的副作用。
正如Peter Parker
所解释的,在没有volatile
修饰符的情况下,每个线程的堆栈可能都有自己的variables副本。 通过将variables设置为volatile
,内存一致性问题已得到解决。
看看jenkov教程页面,以更好的理解。
看看相关的SE问题了解一些关于volatile和用例使用volatile的更多细节:
Java中volatile和synchronized的区别
一个实际的用例:
您有许multithreading,需要以特定格式打印当前时间,例如: java.text.SimpleDateFormat("HH-mm-ss")
。 Yon可以有一个类,它将当前时间转换为SimpleDateFormat
并每隔一秒更新一次variables。 所有其他线程可以简单地使用此易失性variables在日志文件中打印当前时间。
易失variables是轻量级的同步。 当所有线程之间的最新数据的可见性是需要的,并且primefaces性可以被破坏时,在这种情况下,易失variables必须是优选的。 读取volatilevariables总是返回由任何线程完成的最新写入,因为它们既不caching在寄存器中,也不caching在其他处理器无法看到的caching中。 易失性是无锁的。 当情景满足上述标准时,我使用volatile。 更多locking自由和基于locking的策略 。
volatile关键字有两种不同的用法。
- 阻止JVM从寄存器中读取值(假定为caching),并强制从内存读取值。
- 减less内存不一致错误的风险。
阻止JVM读取寄存器中的值,并强制从内存中读取值。
忙标志用于防止线程在设备繁忙时继续运行,并且该标志不受锁的保护:
while (busy) { /* do something else */ }
当另一个线程closures忙标志时,testing线程将继续:
busy = 0;
然而,由于在testing线程中频繁地访问繁忙,所以JVM可以通过将繁忙值置于寄存器中来优化testing,然后在每次testing之前testing寄存器的内容而不读取存储器中的繁忙值。 testing线程永远不会看到繁忙的更改,而另一个线程只会改变内存中的繁忙值,导致死锁。 将忙标志声明为volatile,强制在每次testing之前读取其值。
减less内存一致性错误的风险。
使用volatilevariables可以降低内存一致性错误的风险,因为对volatilevariables的任何写操作都会build立一个“之前发生的”关系,并且随后读取同一个variables。 这意味着对其他线程总是可见的对volatilevariables的更改。
读取,写入没有内存一致性错误的技术称为primefaces操作 。
primefaces动作是一次有效地发生的动作。 primefaces行动不能停在中间,要么完全发生,要么根本不发生。 在动作完成之前,不会看到primefaces动作的副作用。
以下是您可以指定为primefaces的操作:
- 对于引用variables和大多数原始variables(除了long和double之外的所有types),读取和写入是primefaces的。
- 对于声明为volatile的所有variables(包括long和doublevariables),读取和写入是primefaces的。
干杯!
通过在Java应用程序中同时运行线程来asynchronous修改易失性variables 。 不允许有一个variables的本地副本,这个variables与当前保存在“main”内存中的值不同。 实际上,声明为volatile的variables必须使其数据在所有线程中保持同步,以便每当访问或更新任何线程中的variables时,所有其他线程立即看到相同的值。 当然,volatilevariables可能比“plain”variables具有更高的访问和更新开销,因为线程可以拥有自己的数据副本的原因是为了更好的效率。
当一个字段被声明为volatile的时候,编译器和运行时会注意到这个variables是共享的,并且它的操作不应该和其他的内存操作重新sorting。variables不会被caching在寄存器或者caching中,处理器,所以一个volatilevariables的读取总是返回任何线程的最近写入。
作为参考,请参阅http://techno-terminal.blogspot.in/2015/11/what-are-volatile-variables.html