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 天,但仍不能保证看到 num0 不同。不仅如此,如果ThreadRunnableBoth 也有一个以false 开头的非易失性boolean 并在飞跃结束时设置为truemain 也可以看到boolean 为@ 987654341@(这意味着线程已经完成)但num 仍然在0!这是一场数据竞争,并且可能发生在(例如)多核机器上,其中布尔值在num 之前从本地寄存器中刷新。在此示例中,同时创建num 和布尔值volatile 将确保如果boolean 为真,则num == 0 || num == 15

但关键在于:即使没有 volatile 关键字——也就是说,即使存在数据竞争——你也不能保证看到不正当的行为。也就是说,数据竞赛说你不能保证你会在另一个线程中看到变化——但它不能保证你不会。可能是它在你的机器上运行 100 次就好了,然后有人把它放在一个 8 核的机器上,它是一个更复杂的程序的一部分,所以它得到了不同的优化,然后事情就坏了。

【讨论】:

【参考方案6】:

大部分讨论都是关于硬件的。实际上编译器优化通常更相关。您正在以一种小方法重复访问一个字段,所以让我们将它放入一个寄存器中。改变物理内存不会改变寄存器中的值。

虽然(“新”,但多年前)Java 内存模型(JMM)不像旧的那样谈论主内存,也不提供进度保证(很难实际指定),实现 volatile /happens-before 规范将导致从寄存器中逐出并在线程之间进行同步。

【讨论】:

以上是关于java中volatile关键字的行为的主要内容,如果未能解决你的问题,请参考以下文章

说明 C# 中 volatile 关键字的用法

java里volatile关键字有啥特性?

java中volatile关键字的含义--volatile并不能做到线程安全

Java并发编程:volatile关键字解析

你啥时候在 Java 中使用 volatile 关键字? [复制]

java编程,如何彻底理解volatile关键字?