java中volatile关键字最简单易懂的例子
我正在阅读关于Java中的volatile关键字,并完全理解它的理论部分。
但是,我正在寻找的是一个很好的例子,它显示了如果variables不是不稳定的,那么会发生什么。
下面的代码片段不能按预期工作( 来自aioobe )
class Test extends Thread { boolean keepRunning = true; public void run() { while (keepRunning) { } System.out.println("Thread terminated."); } public static void main(String[] args) throws InterruptedException { Test t = new Test(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println("keepRunning set to false."); } }
理想情况下,如果keepRunning不是易失性的,线程应该继续不断地运行。 但是,它几秒钟后停止。
我有两个基本的问题:
- 任何人都可以用例子解释volatile吗? 不符合JLS的理论。
- 挥发替代品是否同步? 它是否实现primefaces性?
挥发性 – >保证可见性,而不是primefaces性
同步(locking) – >保证可视性和primefaces性(如果正确)
挥发性不能代替同步
只有在更新引用而不对其执行其他操作时才使用volatile。
例:
volatile int i = 0; public void incrementI(){ i++; }
不会使用同步或AtomicInteger线程安全作为增量是一个复合操作。
为什么程序不能无限期地运行?
那么这取决于各种情况。 在大多数情况下,JVM足够聪明,可以刷新内容。
正确使用volatile会讨论volatile的各种可能用法。 正确使用volatile会比较棘手,我会说“如果有疑问,请不要使用它”,而是使用synchronized块。
也:
同步块可以用来代替volatile,但是反过来是不正确的 。
举个例子:如果没有声明为volatile,服务器JVM可以将keepRunning
variables提取出循环,因为它在循环中没有被修改(将其变成无限循环),但是客户机JVM不会。 这就是为什么你看到不同的结果。
有关易失variables的一般说明如下:
当一个字段被声明为volatile
,编译器和运行时会被注意到这个variables是共享的,并且它的操作不应该和其他的内存操作重新sorting。 易失性variables不会caching在寄存器或caching中,因为它们对其他处理器是隐藏的,所以读取volatilevariables总是返回任何线程最近的写入 。
易失性variables的可见性效应超出了易失性variables本身的价值。 当线程A写入一个易失性variables,然后线程B读取同一个variables时,写入易失性variables之前A对A可见的所有variables的值在读取volatilevariables后对B变得可见
volatilevariables最常用的用法是完成,中断或状态标志:
volatile boolean flag; while (!flag) { // do something untill flag is true }
易失variables可用于其他types的状态信息,但在尝试此操作时需要多加小心。 例如,volatile的语义不足以使增量操作( count++
)为primefaces,除非可以保证variables只能从单个线程写入。
locking可以保证可见性和primefaces性; volatilevariables只能保证可见性。
只有满足以下所有条件时,才可以使用volatilevariables:
- 写入variables不取决于其当前值,也可以确保只有单个线程更新该值;
- variables不参与与其他状态variables的不variables; 和
- 正在访问variables时,locking不需要任何其他原因。
debugging提示 :确保在调用JVM时始终指定-server JVM命令行开关,即使是开发和testing。 服务器JVM执行比客户端JVM更多的优化,例如将循环中没有修改的variables提取出来; 可能在开发环境(客户端JVM)中工作的代码可能会在部署环境(服务器JVM)中崩溃。
这是“Java并发实践”的摘录,您可以find有关此主题的最佳书籍。
我稍微修改了你的例子。 现在使用keepRunning作为volatile和non volatile成员的例子:
public class TestVolatile extends Thread{ boolean keepRunning = true; public void run() { long count=0; while (keepRunning) { count++; } System.out.println("Thread terminated."+count); } public static void main(String[] args) throws InterruptedException { TestVolatile t = new TestVolatile(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println("keepRunning set to false."); } }
什么是volatile关键字?
volatile关键字可以防止
caching of variables
。
考虑代码,首先没有易变的关键字
class MyThread extends Thread { private boolean running = true; //non-volatile keyword public void run() { while (running) { System.out.println("hello"); } } public void shutdown() { running = false; } } public class Main { public static void main(String[] args) { MyThread obj = new MyThread(); obj.start(); Scanner input = new Scanner(System.in); input.nextLine(); obj.shutdown(); } }
理想情况下 ,这个程序应该print hello
直到RETURN key
被按下。 但是在some machines
,可能会发生variables运行被cached
并且不能从shutdown()方法改变它的值,从而导致hello文本的infinite
打印。
因此,使用volatile关键字,可以guaranteed
你的variables不会被caching,也就是说,在all machines
上all machines
run fine
。
private volatile boolean running = true; //volatile keyword
因此使用volatile关键字是一个good
和safer programming practice
。
当一个variables是volatile
,它保证它不会被caching,不同的线程会看到更新的值。 但是,不标记它的volatile
并不能保证相互对立。 volatile
是那些在JVM中被打破了很长时间的东西之一,但仍然不是很好理解。
volatile
并不一定会造成巨大的变化,这取决于JVM和编译器。 然而,对于许多(边缘)情况,可能是优化导致variables的变化不能被注意到,而不是被正确写入。
基本上,优化器可以select将非易失性variables放在寄存器或堆栈上。 如果另一个线程在堆或类的原语中改变它们,另一个线程将继续在堆栈中查找它,并且它将是陈旧的。
volatile
确保这种优化不会发生,所有读写操作都直接到堆或所有线程都能看到的地方。
请find下面的解决scheme,
这个variables的值永远不会被caching在本地线程中:所有的读写操作都将直接进入“主内存”。 volatile会强制线程每次更新原始variables。
public class VolatileDemo { private static volatile int MY_INT = 0; public static void main(String[] args) { ChangeMaker changeMaker = new ChangeMaker(); changeMaker.start(); ChangeListener changeListener = new ChangeListener(); changeListener.start(); } static class ChangeMaker extends Thread { @Override public void run() { while (MY_INT < 5){ System.out.println("Incrementing MY_INT "+ ++MY_INT); try{ Thread.sleep(1000); }catch(InterruptedException exception) { exception.printStackTrace(); } } } } static class ChangeListener extends Thread { int local_value = MY_INT; @Override public void run() { while ( MY_INT < 5){ if( local_value!= MY_INT){ System.out.println("Got Change for MY_INT "+ MY_INT); local_value = MY_INT; } } } } }
请参阅http://java.dzone.com/articles/java-volatile-keyword-0这个链接,以获得更多的清晰度。;
如果你声明一个variables为volatile,那么它将不会被存储在本地caching中。 每当线程更新值时,它都会更新到主内存。 所以,其他线程可以访问更新的值。
理想情况下,如果keepRunning不是不稳定的,线程应该保持无限期运行。 但是,它几秒钟后停止。
如果您正在单处理器中运行,或者如果系统非常繁忙,则操作系统可能会交换导致某些级别的caching失效的线程。 正如其他人所说,没有volatile
并不意味着内存不会被共享,但JVM试图不会同步内存,如果它可以出于性能的原因,所以内存可能不会更新。
另外需要注意的是System.out.println(...)
是同步的,因为底层的PrintStream
是通过同步来停止重叠的输出。 所以你在主线程中获得了“免费”的内存同步。 这仍然不能解释为什么阅读循环看到所有的更新。
无论println(...)
行是在或不在,您的程序在Java6下通过Intel i7在MacBook Pro上为我旋转。
任何人都可以用例子解释volatile吗? 不符合JLS的理论。
我认为你的榜样是好的。 不知道为什么它不能处理所有的System.out.println(...)
语句。 这个对我有用。
挥发替代品是否同步? 它是否实现primefaces性?
在存储器同步方面, volatile
与synchronized
模块一样具有相同的内存屏障,除了volatile
屏障是单向而不是双向的。 volatile
读取会导致负载障碍,而写入则会导致存储障碍。 synchronized
块是双向屏障。
然而,在atomicity
,答案是“取决于”。 如果您正在读取或写入字段的值,那么volatile
提供适当的primefaces性。 然而,增加一个volatile
字段的限制是++
实际上是三个操作:读取,增加,写入。 在这种情况下或更复杂的互斥体情况下,一个完整的synchronized
块是有序的。
声明为volatile的对象通常用于在线程之间传递状态信息,以确保CPU高速caching更新,即在存在易失性字段,CPU指令,内存屏障(通常称为membar)的情况下保持同步围栏,发射更新CPUcaching与变化的字段的值的变化。
volatile修饰符告诉编译器volatile修改的variables可以被程序的其他部分意外地改变。
volatilevariables只能在线程上下文中使用。 看到这里的例子