[英]Why Volatile is behaving weirdly
我最近经历过volatile关键字这种奇怪的行为。 我所知道的,
volatile关键字应用于变量,以将一个线程对变量数据所做的更改反映到另一个线程上。
volatile关键字阻止在线程上缓存数据。
我做了一个小测试........
我使用了一个名为count的整数变量,并在其上使用了volatile关键字。
然后制作2个不同的线程将变量值增加到10000,因此最终结果应为20000。
但事实并非如此,使用volatile关键字我一直没有得到20000,而是18534,15000等......有时候是20000。
但是虽然我使用了synchronized关键字,但它工作得很好,为什么......?
任何人都可以解释我挥发性关键字的这种行为。
我发布带有volatile关键字的代码,以及带有synchronzied关键字的代码。
以下代码与变量计数上的volatile关键字行为不一致
public class SynVsVol implements Runnable{
volatile int count = 0;
public void go(){
for (int i=0 ; i<10000 ; i++){
count = count + 1;
}
}
@Override
public void run() {
go();
}
public static void main(String[] args){
SynVsVol s = new SynVsVol();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Total Count Value: "+s.count);
}
}
以下代码与方法go()上的synchronized关键字完美匹配。
public class SynVsVol implements Runnable{
int count = 0;
public synchronized void go(){
for (int i=0 ; i<10000 ; i++){
count = count + 1;
}
}
@Override
public void run() {
go();
}
public static void main(String[] args){
SynVsVol s = new SynVsVol();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Total Count Value: "+s.count);
}
}
count = count + 1
不是原子的。 它有三个步骤:
这三个步骤交织在一起,导致不同的执行路径,导致不正确的值。 如果要避免使用synchronized关键字,请使用AtomicInteger.incrementAndGet()
。
因此,尽管volatile关键字的行为与您所描述的几乎相同,但它仅适用于每个单独的操作,而不适用于所有三个操作。
volatile
关键字不是同步原语。 它只是防止在线程上缓存值,但它不会阻止两个线程修改相同的值并同时将其写回。
假设有两个线程需要递增计数器,现在设置为5.两个线程都看到5,从中取出6,并将其写回计数器。 如果计数器不是volatile
,则两个线程都可以假设它们知道值为6,并跳过下一个读取。 然而,它是不稳定的,所以它们都会读回6,并继续递增。 由于线程没有进入锁定步骤,您可能会在输出中看到与10000不同的值,但实际上几乎没有机会看到20000。
变量是volatile
这一事实并不意味着它所涉及的每个操作都是原子的。 例如, SynVsVol.Go
这一行:
count = count + 1;
将首先count
读数,然后递增,然后将结果写回。 如果某个其他线程将同时执行它,结果取决于命令的交错。
现在,当您添加syncronized
, SynVsVol.Go
以原子SynVsVol.Go
执行。 也就是说,增量是由一个线程整体完成的,另一个在完成之前不能修改count
。
最后,缓存仅在同步块中修改的成员变量要容易得多。 获取监视器时,编译器可以读取它们的值,将其缓存在寄存器中,对该寄存器进行所有更改,并在释放监视器时最终将其刷回主存储器。 当您在同步块中调用wait
时,以及当其他某个线程notify
您时,情况也是如此:缓存的成员变量将被同步,并且您的程序将保持一致。 即使成员变量未声明为volatile,也能保证这一点:
同步确保线程在同步块之前或期间的内存写入以可预测的方式显示给在同一监视器上同步的其他线程。
您的代码被破坏了,因为它将volatile
上的读取和递增操作视为原子,而不是。 代码不包含数据争用,但它确实包含int
的竞争条件 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.