java volatile 求解

OLESHI 发布于 2018/08/21 11:26
阅读 564
收藏 0

代码如下:

public class Volatile {
    private static volatile boolean bChanged=false;
    public static void main(String[] args) throws InterruptedException {
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (bChanged == !bChanged) {
                        System.out.println("bChanged: "+bChanged+"  !bChanged="+(!bChanged));
                        System.out.println("Thread 1 exit!");
                        System.exit(0);
                    }
                    
                }
            }
        }.start();
        Thread.sleep(1);
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    bChanged = !bChanged;
                }
            }
        }.start();
    }

}

为啥以上代码System.exit(0);一定会执行?

使用了volatile关键字,【bChanged == !bChanged】不应该永远是false吗?

难道是因为【bChanged == !bChanged】要取两次值?

加载中
0
tcxu
tcxu

楼主问:为啥以上代码System.exit(0);一定会执行?
答:不是一定会执行。只有在第一个线程 run()方法里的 逻辑表达式(bChanged == !bChanged) 的值为 true 的时候才得以执行。

楼主问:使用了volatile关键字,【bChanged == !bChanged】不应该永远是false吗?
答: 使用了volatile关键字,一旦启动第二个线程,第一个线程在读取 bChanged 时, 就会看到 第二个线程执行 bChanged = !bChanged 的后果。这样,第一个线程 才会在"关键时段"(见上一贴)读取到不同(相反)的 bChanged 值,致使 bChanged == !bChanged 为 true, 结果,System.exit(0) 才会执行,导致程序终止。

楼主问:难道是因为【bChanged == !bChanged】要取两次值?
答: 是的。每次计算逻辑表达式 (bChanged == !bChanged)的值的时候, 都要先后两次去读取 bChanged 的值。

O
OLESHI
perfect!
1
Rhys_Lee
Rhys_Lee

volatile只能保证可见性和有序性,并不能保证原子性。你说的没错,bChanged==!bChanged会取两次值。

还有,sout出来的bChanged和!bChanged也是两次取值。所以,打印出来的两个值可能不等。

1
tcxu
tcxu

摘要


实验涉及 2 个线程,它们共享变量: static boolean bChanged。

判断逻辑表达式 (bChanged == !bChanged) 的值, 要经过一个两次(先后)读取 bChanged 变量值的过程。如果前后两次取的值永远相同,则(bChanged == !bChanged)永远为 false。就是说,第一个线程  run() 方法里的 while(true){.....}循环体里的代码块 if (bChanged == !bChanged) {......} 永远不会执行,程序永远不会终止。
执行第二个线程的 run() 方法里的 while(true){.....}循环体里的代码 bChanged = !bChanged, 旨在将共享变量 bChanged 取反。
只有第二个线程反转共享变量 bChanged,致使 第一个线程在"关键时段"读取到不同的 bChanged 值,才会造成整个程序运行终止。
每个线程 在自己的工作内存里都复制了一份主内存里的共享变量 bChanged 的副本拷贝,该线程仅对这个副本拷贝进行所有操作(读取、赋值)。因此,两个线程"步入正轨"之后,它们仅与自己的副本拷贝 进行所有操作(读取、赋值)。
我的机器实验表明,第一个线程启动后 5 毫秒之后 (  Thread.sleep(5);),再启动第二个线程,就会使两个线程"步入正轨"。其结果是,程序永远不终止。
如果在第一个线程启动后 5 毫秒之内,启动第二个程序,则能立即终止程序运行,因为此刻线程尚未"步入正轨"。
加关键字 volatite (可见性),可以使第一个程序在读取共享变量 bChanged 的时候,立即看到其它线程对此变量的修改。因此,无论何时启动第二个线程,都将立即终止程序运行。

正文

 

        判断逻辑表达式 (bChanged == !bChanged) 的值, 要有一个过程:

  1. 取 变量 bChanged 的值;
  2. 取其反,即实行一元逻辑操作符 ! 和 算子bChanged 的值的计算;
  3. 第二次取 变量 bChanged 的值 (前后两次取 bChanged 值的时间段 称为"关键时段" );
  4. 将 2 和 3 的结果进行比较,就是说,通过二元逻辑操作符 == 实行 两个算子 (bChanged 和 !bChanged) 的值的比较。

        由此可知:
1.   如果,前后两次取的值永远相同,则(bChanged == !bChanged)永远为 false。就是说,第一个线程  run()方法里的while(true){.....}循环体里的代码块 if (bChanged == !bChanged) {......} 永远不会执行,程序永远不会终止。
演示:删去 volatile, 再删去 Thead.sleep(1); 以及下面定义第二个线程的代码。只运行第一个线程,就可以看到,程序永不终止。
2.   只有第二个线程启动后,并且由于运行 bChanged = !bChanged; 使第一个线程在"关键时段" 读取到不同的 bChanged 值, if (bChanged == !bChanged) {......} 才会执行,程序才会终止。
演示:如楼主在启动第一个线程 1 毫秒后运行第二个线程所得到的运行终止的结果。

        众所周知,"Java内存模型分为主内存,和工作内存。主内存是所有的线程所共享的,工作内存是每个线程自己有一个,不是共享的"。
"每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。线程对变量的所有操作(读取、赋值),都必须在工作内存中进行,而不能直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者之间的交互关系如图"。
实验一: 在共享静态变量 bChanged 前面不加关键词  volatile
共享变量 bChanged=false,在加载的时候,就帅先存入主内存。然后两个线程相继启动:分别将 boolean bChanged=false 存入各自的工作内存。我的实验表明,
1. 若在第一个线程启动后的 5 毫秒之内启动第二个线程,第二个线程就有机会在加载、分配内存时,在"关键时段",使第一个线程读取到不同的 bChanged 值, 从而终止整个程序运行。 
2. 若在第一个线程启动后的 5 毫秒之后再启动第二个线程,第二个线程就没有能力在"关键时段" 让第一个线程读取到不同的 bChanged 值, 从而无法终止整个程序运行。
演示:将 Tread.sleep(1); 改成:  Tread.sleep(6); 或更长睡眠时间就会发现,整个程序运行就不会终止。

实验二:加关键字 volatite (可见性)
这样,就可以使得第一个程序在读取共享变量 bChanged 的时候,立即看到其它线程对此变量的修改。
因此,无论何时启动第二个线程,只要一启动第二个线程,第一个线程在读取 bChanged时, 就会看到 第二个线程执行 bChanged = !bChanged 的后果, 结果读到不同的 bChanged 值,致使程序终止。
演示:在共享变量 bChanged 前加 关键字 volatite。睡觉时间定为:   Tread.sleep(1000), 就会看到,程序启动1秒钟后 终止。

参考:


Java---线程多(工作内存)和内存模型(主内存)分析

翻译:

 

Atomic Access

"...Any write to a volatile variable establishes a happens-before relationship with subsequent reads of that same variable.  This means that changes to a volatile variable are always visible to other threads. What's more, it also means that when a thread reads a volatile variable, it sees not just the latest change to the volatile, but also the side effects of the code that led up the change." 
任何冠以 关键词 volatile 的变量,都建立了一种  "发生在前 happens-before" 关系,即给一个变量赋值,一定发生在读取这个变量的数值之前。这就是说,一个 volatile 变量的变化, 总是对其它线程来讲是可见的。不仅如此,volatile 还意味着,不仅变化可见,而且导致改变的这个代码,其副作用,也可见。

0
maskleo
maskleo

A 与 B 比较

先拿到A 再拿到 B 再比较  而这个过程中AB都可能变化

O
OLESHI
我猜也是
0
开源中国首席屌丝
开源中国首席屌丝

正常人不会这么写代码

0
f
foolish102

A,B两个值可能会变化没错,但也不是一定会执行条件内的代码吧?

O
OLESHI
有办法验证他不会执行吗?
0
兴百姓苦_亡百姓苦

代码还能这么写?

0
沉浮_
沉浮_

其实就是第一次取到值后,还没比较,取反时值已经被改变,导致执行了if代码,其实你可以 改下代码

private static volatile boolean is = false;

public static void main(String[] args) throws InterruptedException {

    new Thread(()->{
        while (true){
            if(is == !is){
                System.out.println("+++++++++++++++++ "+is);
            }else{
                System.out.println("----------------- "+is);
            }
        }

    }).start();


    Thread.sleep(1000);

    new Thread(()->{
        while (true){
            is = !is;
        }
    }).start();
}

你搜说的一定会执行,其实是在千百次中有一次成立就执行了

0
返回顶部
顶部