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可以将keepRunningvariables提取出循环,因为它在循环中没有被修改(将其变成无限循环),但是客户机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 machinesall machines run fine

 private volatile boolean running = true; //volatile keyword 

因此使用volatile关键字是一个goodsafer 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性?

在存储器同步方面, volatilesynchronized模块一样具有相同的内存屏障,除了volatile屏障是单向而不是双向的。 volatile读取会导致负载障碍,而写入则会导致存储障碍。 synchronized块是双向屏障。

然而,在atomicity ,答案是“取决于”。 如果您正在读取或写入字段的值,那么volatile提供适当的primefaces性。 然而,增加一个volatile字段的限制是++实际上是三个操作:读取,增加,写入。 在这种情况下或更复杂的互斥体情况下,一个完整的synchronized块是有序的。

声明为volatile的对象通常用于在线程之间传递状态信息,以确保CPU高速caching更新,即在存在易失性字段,CPU指令,内存屏障(通常称为membar)的情况下保持同步围栏,发射更新CPUcaching与变化的字段的值的变化。

volatile修饰符告诉编译器volatile修改的variables可以被程序的其他部分意外地改变。

volatilevariables只能在线程上下文中使用。 看到这里的例子