volatile关键字的作用

2018-10-11  本文已影响402人  baby_honour

在Java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。

Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块(synchronized) 和 volatile 关键字机制。

synchronized
同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用
synchronized 修饰的方法 或者 代码块。

volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。

如果要深入了解volatile关键字的作用,就必须先来了解一下JVM在运行时候的内存分配过程。
在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。


JVM在运行时候的内存分配过程
1   public class VolatileTest extends Thread { 
2 
 3     boolean flag = false;
 4     int i = 0;
 5     
 6     public void run() {
 7         while (!flag) {
 8             i++;
 9 } 
10 }
 11     
12     public static void main(String[] args) throws Exception { 
13         VolatileTest vt = new VolatileTest(); 
14         vt.start(); 
15         Thread.sleep(2000); 
16         vt.flag = true; 
17         System.out.println("stope" + vt.i); 
18 } 
19 }

上面的代码是通过标记flag来控制VolatileTest线程while循环退出的例子!

上面的叙述看似并没有什么问题,“似乎”完全正确。那就让我们把程序运行起来看看效果吧,执行mian方法。2秒钟以后控制台打印stope-202753974。

可是奇怪的事情发生了 程序并没有退出。vt线程仍然在运行,也就是说在主线程设置的 vt.flag = true;没有起作用。

在这里需要说明一下,可能在测试上面代码的时候程序可以正常退出。那是因为你的JVM没有优化造成的!在DOC下面输入 java -version 查看 如果显示Java HotSpot(TM) ... Server 则JVM会进行优化。

如果显示Java HotSpot(TM) ... Client 为客户端模式,需要设置成Server模式 设置方法问Google

cmd java -version

问题出现了,为什么我在主线程(main)中设置了vt.flag = true; 而vt线程在进行判断flag的时候拿到的仍然是false?

那么按照我们上面所讲的 “JVM在运行时候的内存分配过程” 就很好解释上面的问题了。

首先 vt线程在运行的时候会把 变量 flag 与 i (代码3,4行)从“主内存” 拷贝到 线程栈内存(上图的线程工作内存)

然后 vt线程开始执行while循环

7    while (!flag) {
8          i++; 
9    }

while (!flag)进行判断的flag 是在线程工作内存当中获取,而不是从 “主内存”中获取。

i++; 将线程内存中的i++; 加完以后将结果写回至 "主内存",如此重复。

再说说主线程的执行过程

vt.flag = true;

主线程将vt.flag的值同样 从主内存中拷贝到自己的线程工作内存 然后修改flag=true. 然后再将新值回到主内存。

这就解释了为什么在主线程(main)中设置了vt.flag = true; 而vt线程在进行判断flag的时候拿到的仍然是false。那就是因为vt线程每次判断flag标记的时候是从它自己的“工作内存中”取值,而并非从主内存中取值!

这也是JVM为了提供性能而做的优化。那如何能让vt线程每次判断flag的时候都强制它去主内存中取值呢。这就是volatile关键字的作用。

再次修改代码

public class VolatileTest extends Thread { 
       volatile boolean flag = false;
       int i = 0; 
public void run() { 
       while (!flag) {
            i++;
       }
} 
public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();
        Thread.sleep(2000);
        vt.flag = true;
        System.out.println("stope" + vt.i);
    }
}

在flag前面加上volatile关键字,强制线程每次读取该值的时候都去“主内存”中取值。在试试程序吧,已经正常退出了。

原文参考:https://www.cnblogs.com/daxin/p/3364014.html

上一篇 下一篇

猜你喜欢

热点阅读