应该使用可以certificate“volatile”声明的代码示例

目前我无法理解何时应该使用volatile来声明变量。

我已经做了一些研究并且长时间搜索了一些关于它的材料,并且知道当一个字段被声明为volatile时,编译器和运行时会注意到这个变量是共享的,并且对它的操作不应该与其他内存重新排序操作。

但是,我仍然无法理解在什么情况下我们应该使用它。 我的意思是,有人可以提供任何示例代码,可以certificate使用“volatile”带来的好处或解决问题与不使用它相比?

这是为什么必须使用volatile的示例。 如果删除关键字volatile ,则线程1 可能永远不会终止。 (当我在Linux上测试Java 1.6 Hotspot时,确实如此 – 您的结果可能会有所不同,因为JVM没有义务对没有标记为volatile的变量进行任何缓存。)

 public class ThreadTest { volatile boolean running = true; public void test() { new Thread(new Runnable() { public void run() { int counter = 0; while (running) { counter++; } System.out.println("Thread 1 finished. Counted up to " + counter); } }).start(); new Thread(new Runnable() { public void run() { // Sleep for a bit so that thread 1 has a chance to start try { Thread.sleep(100); } catch (InterruptedException ignored) { // catch block } System.out.println("Thread 2 finishing"); running = false; } }).start(); } public static void main(String[] args) { new ThreadTest().test(); } } 

以下是volatile的必要性的典型示例(在这种情况下为str变量。没有它,hotspot提升循环外的访问( while (str == null) )和run()永远不会终止。这将发生在大多数服务器JVM。

 public class DelayWrite implements Runnable { private String str; void setStr(String str) {this.str = str;} public void run() {  while (str == null);  System.out.println(str); } public static void main(String[] args) {  DelayWrite delay = new DelayWrite();  new Thread(delay).start();  Thread.sleep(1000);  delay.setStr("Hello world!!"); } } 

埃里克,我已经阅读了你的评论,其中一个特别让我感到震惊

事实上,我可以在概念层面理解volatile的用法。 但是对于实践,我不能在没有使用volatile的情况下想出具有并发性问题的代码

您可以遇到的明显问题是编译器重新排序,例如Simon Nickerson提到的更着名的吊装。 但是我们假设没有重新排序,评论可以是有效的。

volatile解析的另一个问题是64位变量(long,double)。 如果您写入long或double,则将其视为两个单独的32位存储。 并发写入会发生什么是一个线程的高32写入寄存器的高32位而另一个线程写入低32位。 然后你可以拥有一个既不是一个也不是另一个的长。

此外,如果你看一下JLS的内存部分,你会发现它是一个放松的内存模型。

这意味着写入可能不会变得可见(可以坐在存储缓冲区中)一段时间。 这可能导致陈旧的读取。 现在你可能会说这似乎不太可能,但确实如此,但你的程序是不正确的,有可能失败。

如果你有一个int,你在应用程序的生命周期中递增,并且你知道(或者至少认为)int不会溢出,那么你不会将它升级为long,但它仍然可以。 在内存可见性问题的情况下,如果您认为它不会影响您,您应该知道它仍然可以并且可能导致并发应用程序中极难识别的错误。 正确性是使用挥发性的原因。

volatile关键字非常复杂,您需要在使用它之前了解它的function并且不能很好地执行。 我建议阅读这个语言规范部分 ,它解释得非常好。

他们强调了这个例子:

 class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } } 

这意味着在one()期间j永远不会比i 。 但是,运行two()另一个Thread可能会打印出比i大得多的j值,因为假设有two()正在运行并获取i的值。 然后one()运行1000次。 然后,运行两个的线程最终再次被调度并且拾取j ,其现在远大于i的值。 我认为这个例子完美地展示了volatile和synchronized之间的区别 – 对ij的更新是volatile,这意味着它们发生的顺序与源代码一致。 但是,这两个更新是分开进行的,而不是自动进行的,因此调用者可能会看到(对于该调用者)看起来不一致的值。

简而言之:要小心volatile

java 8中的极简主义示例,如果删除volatile关键字,它将永远不会结束。

 public class VolatileExample { private static volatile boolean BOOL = true; public static void main(String[] args) throws InterruptedException { new Thread(() -> { while (BOOL) { } }).start(); TimeUnit.MILLISECONDS.sleep(500); BOOL = false; } }