java中volatile关键字的行为
Posted
技术标签:
【中文标题】java中volatile关键字的行为【英文标题】:behaviour of volatile keyword in java 【发布时间】:2012-04-01 01:38:48 【问题描述】:我需要一些关于 Java 线程的 Volatile Keyword 的示例。
根据 volatile 关键字的定义,当变量声明为 volatile 时,线程将直接读取/写入变量内存,而不是从本地线程缓存读取/写入。
如果我错了,请纠正我。
因此,当我运行以下程序时,
public class ThreadRunnableBoth implements Runnable
private volatile int num =0;
public void run()
Thread t = Thread.currentThread();
String name = t.getName();
for(int i=0; i<100; i++)
if(name.equals("Thread1"))
num=10;
System.out.println("value of num 1 is :"+num);
else
num=15;
System.out.println("value of num 2 is :"+num);
public static void main(String args[]) throws InterruptedException
Runnable r = new ThreadRunnableBoth();
Thread t1 = new Thread(r);
t1.setName("Thread1");
Thread t2 = new Thread(r);
t2.setName("Thread2");
t1.start();
t2.start();
我从某个站点获得了这些示例,当我尝试运行它时,我看不出删除 Volatile 或添加 Volatile 关键字有什么区别。
请解释一下删除和添加的区别。
非常感谢。
【问题讨论】:
【参考方案1】:有没有volatile
关键字的主要区别在于你是否需要一个内存栅栏来安全地操作数据。
内存栅栏可防止由于乱序执行而在多个线程之间发生的副作用。通过指令CPU,编译器/运行时环境可以告诉CPU,在不破坏程序正确性的情况下,无法对读取的原始排序约束进行操作。
Read up on memory fences here,并记住解决方案的关键是一致性,而不是位置。读取请求可以在缓存处停止,只要保证缓存一致(通过 CPU 的内部机制)。
【讨论】:
【参考方案2】:根据 volatile 关键字的定义,它说,当变量是 声明为 volatile 然后线程将直接读/写变量 内存而不是从本地线程缓存读取/写入。
不一定。支持cache coherence 的系统可以使易失性字段保持最新,而无需从主存储器中读取。 Volatile 表示每个线程都会看到某个字段的最新值。
至于内存可见性,如果您删除 volatile 但您的程序可能会失败,您不一定会(立即)看到任何更改。它运行的时间越长,您最终看到的问题就越多。
【讨论】:
【参考方案3】:所以没有 volatile
关键字,线程只是在其本地内存缓存中打印num
的值。他们对num
的更改绝不会与其他线程对num
的视图同步。我看到如下输出:
value of num 1 is :10
value of num 2 is :15
value of num 1 is :10
value of num 2 is :15
value of num 1 is :10
value of num 2 is :15
...
使用volatile
,它们都在更新和打印到相同的全局存储位置,并且设置/获取周围有内存屏障。但这不会改变非常受竞争条件影响的输出。我看到如下输出:
value of num 2 is :15
value of num 1 is :15
value of num 2 is :15
value of num 1 is :10
value of num 2 is :15
value of num 1 is :10
...
在打印值时,哪一组是最后一个。
您可能看不到此输出,因为您的处理器架构或 JRE 仅在 IO 事件上进行上下文切换,或者不提供完整的线程执行。如果您显示一些输出,那么我可以发表更多评论。
【讨论】:
【参考方案4】:volatile 变量的影响在multiprocessor
系统上很明显,其中不同的线程在不同的处理器上运行。在普通的单处理器系统上,影响可能并不明显。
这里是 good discussion thread 在这个网站上的同一主题。
【讨论】:
【参考方案5】:在您的示例中,num
以默认值 0
开头,然后您(在其声明行)将其分配给 0
。如果num
不是volatile
,那么这项分配将是一场数据竞赛,但你当然无法区分。
然后你只在一个线程中使用num
,并且在一个线程中你总是会看到事情按照代码所说的顺序发生。所以在这种情况下,num
不必是 volatile。
现在,如果您修改了 main
方法,使其在线程启动后检查 t1.num
(但没有检查它是否以创建发生前边缘的方式完成,例如 Thread.join
),如果num
不稳定,您将面临数据竞争。您可以让 main
等待 5 天,但仍不能保证看到 num
与 0
不同。不仅如此,如果ThreadRunnableBoth
也有一个以false
开头的非易失性boolean
并在飞跃结束时设置为true
,main
也可以看到boolean
为@ 987654341@(这意味着线程已经完成)但num
仍然在0
!这是一场数据竞争,并且可能发生在(例如)多核机器上,其中布尔值在num
之前从本地寄存器中刷新。在此示例中,同时创建num
和布尔值volatile
将确保如果boolean
为真,则num == 0 || num == 15
。
但关键在于:即使没有 volatile
关键字——也就是说,即使存在数据竞争——你也不能保证看到不正当的行为。也就是说,数据竞赛说你不能保证你会在另一个线程中看到变化——但它不能保证你不会。可能是它在你的机器上运行 100 次就好了,然后有人把它放在一个 8 核的机器上,它是一个更复杂的程序的一部分,所以它得到了不同的优化,然后事情就坏了。
【讨论】:
【参考方案6】:大部分讨论都是关于硬件的。实际上编译器优化通常更相关。您正在以一种小方法重复访问一个字段,所以让我们将它放入一个寄存器中。改变物理内存不会改变寄存器中的值。
虽然(“新”,但多年前)Java 内存模型(JMM)不像旧的那样谈论主内存,也不提供进度保证(很难实际指定),实现 volatile /happens-before 规范将导致从寄存器中逐出并在线程之间进行同步。
【讨论】:
以上是关于java中volatile关键字的行为的主要内容,如果未能解决你的问题,请参考以下文章
java中volatile关键字的含义--volatile并不能做到线程安全