What is the volatile keyword useful for
在今天的工作中,我遇到了Java中的EDCOX1×0关键字。由于不太熟悉,我发现了以下解释:
Java theory and practice: Managing volatility
号
考虑到这篇文章对所讨论的关键字的详细解释,您是否曾经使用过它,或者您是否曾经看到过这样一种情况:您可以以正确的方式使用这个关键字?
回答您的问题:是的,我使用一个
我非常推荐的"实践中的Java并发"一书,对EDCOX1的0个解释给出了一个很好的解释。这本书是由写IBM文章的同一个人写的(事实上,他在文章的底部引用了他的书)。我使用
如果您想了解更多关于EDOCX1 0如何在引擎盖下工作,请阅读Java内存模型。如果您想超越这个水平,请阅读像Hennessy&Patterson这样的优秀计算机体系结构书籍,并阅读有关缓存一致性和缓存一致性的内容。
"…volatile修饰符保证任何读取字段的线程都能看到最近写入的值。"-josh bloch如果您想使用
关于
来源
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; } } |
我们在第一个请求出现时懒散地创建实例。
如果我们不使
为什么会这样?因为读线程不进行任何锁定,直到写线程从同步块中出来,内存将不会同步,主内存中的
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的变量只有在对象的构造函数完全完成执行后,其他线程才能看到。来源
。
在Java中使用EDCOX1 0的用法:
fail fast迭代器通常使用list对象上的
- 更新列表时,计数器递增。
- 创建
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 } } |
注意如何不需要同步
使用
用
如果我们有一个易失变量,它就不能被任何线程缓存到计算机(微处理器)的缓存内存中。总是从主内存访问。
如果有一个写操作正在对一个易失变量进行,并且突然请求一个读操作,则可以保证写操作将在读操作之前完成。
以上两个品质可以推断
- 所有读取可变变量的线程都将绝对读取最新的值。因为没有缓存值会污染它。而且,只有在当前写入操作完成后,才会授予读取请求。
另一方面,
- 如果我们进一步研究我提到的2,我们可以看到,
volatile 关键字是维护共享变量的理想方法,该共享变量具有n个读线程,只有一个写线程可以访问它。一旦我们添加了volatile 关键字,就完成了。关于线程安全没有任何其他开销。
反过来说,
我们不能仅仅使用
没有人提到对长双变量类型的读写操作的处理。读和写是引用变量和大多数基元变量的原子操作,除了长变量和双变量类型之外,它们必须使用volatile关键字进行原子操作。@链接
是的,只要您希望多个线程访问可变变量,就必须使用volatile。它不是很常见的用例,因为通常您需要执行不止一个原子操作(例如,在修改变量之前检查它的状态),在这种情况下,您将使用同步块。
在我看来,除了停止使用volatile关键字的线程外,还有两个重要的场景:
如果您正在开发一个多线程应用程序,您需要使用"volatile"关键字或"synchronized"以及任何其他您可以使用的并发控制工具和技术。这种应用程序的例子是桌面应用程序。
如果您正在开发一个将部署到应用服务器(Tomcat、JBoss AS、Glassfish等)的应用程序,则不必像应用服务器已经解决的那样处理并发控制。事实上,如果我正确记得JavaEE标准禁止servlet和EJB中的任何并发控制,因为它是"基础设施"层的一部分,您应该释放它。只有在实现singleton对象的情况下,才能在此类应用程序中执行并发控制。如果您使用框架结构(如Spring)编织组件,甚至已经解决了这一问题。
因此,在大多数应用程序是Web应用程序和使用IOC框架(如Spring或EJB)的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); } } } |
是的,我经常使用它——它对于多线程代码非常有用。你指的那篇文章不错。尽管有两件重要的事情要记住:
当然,是的。(不仅在Java中,而且在C语言中),有时需要获取或设置保证在给定平台上的原子操作的值,例如int或布尔值,但不需要线程锁定的开销。volatile关键字允许您确保在读取当前值的值时,而不是刚被另一线程上的写入操作废弃的缓存值。
访问可变字段的每个线程在继续之前都将读取其当前值,而不是(可能)使用缓存值。
只有成员变量可以是可变的或瞬时的。
挥发性变量是光加权同步。如果所有线程中最新数据的可见度都是需要的,而且原子性可能受到损害,那么在这种不稳定变量的情况下,必须预先确定数据的可见性。读取关于挥发性变量的最新文献,因为这些变量在寄存器或在其他处理器看不到的藏匿处都需要隐藏。挥发性是锁着的。我使用的是挥发性的,当剧本遇到前说过的标准。
volatile关键字有两种不同的用法。
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.
这意味着变化为
在没有
请看Jenkov教学页,以便更好地理解。
注意到一些关于挥发性&使用情况的相关问题:
爪哇挥发性与同步性的差异
One practical use case:
你有许多线索,需要以特定格式打印当前时间:
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。
下面是一个非常简单的代码,用于演示
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()); } } |
当不使用
1 | Stopping on: 1895303906650500 |
号
当
1 2 | Stopping on: 1895285647980000 324565439> Stopped on: 1895285648087300 |
演示:https://repl.it/repls/silveragonizingobjectcode