volatile用处说明

在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。

在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
要解决这个问题,就需要把变量声明为volatile(也可以使用同步,参见http://blog.csdn.net/ns_code/article/details/17288243),这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下,各任务间共享的变量都应该加volatile修饰符。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才将私有拷贝与共享内存中的原始值进行比较。
这样当多个线程同时与某个对象交互时,就必须注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示JVM:对于这个成员变量,不能保存它的私有拷贝,而应直接与共享成员变量交互。
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。
使用建议:在两个或者更多的线程需要访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。
由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。

示例程序

下面给出一段代码,通过其运行结果来说明使用关键字volatile产生的差异,但实际上遇到了意料之外的问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class Volatile extends Object implements Runnable {
    //value变量没有被标记为volatile
    private int value; 
    //missedIt变量被标记为volatile
    private volatile boolean missedIt;
    //creationTime不需要声明为volatile,因为代码执行中它没有发生变化
    private long creationTime;
    public Volatile() {
        value = 10;
        missedIt = false;
        //获取当前时间,亦即调用Volatile构造函数时的时间
        creationTime = System.currentTimeMillis();
    }
    public void run() {
        print("entering run()");
        //循环检查value的值是否不同
        while ( value < 20 ) {
            //如果missedIt的值被修改为true,则通过break退出循环
            if  ( missedIt ) {
                //进入同步代码块前,将value的值赋给currValue
                int currValue = value;
                //在一个任意对象上执行同步语句,目的是为了让该线程在进入和离开同步代码块时,
                //将该线程中的所有变量的私有拷贝与共享内存中的原始值进行比较,
                //从而发现没有用volatile标记的变量所发生的变化
                Object lock = new Object();
                synchronized ( lock ) {
                    //不做任何事
                }
                //离开同步代码块后,将此时value的值赋给valueAfterSync
                int valueAfterSync = value;
                print("in run() - see value=" + currValue +", but rumor has it that it changed!");
                print("in run() - valueAfterSync=" + valueAfterSync);
                break;
            }
        }
        print("leaving run()");
    }
    public void workMethod() throws InterruptedException {
        print("entering workMethod()");
        print("in workMethod() - about to sleep for 2 seconds");
        Thread.sleep(2000);
        //仅在此改变value的值
        value = 50;
        print("in workMethod() - just set value=" + value);
        print("in workMethod() - about to sleep for 5 seconds");
        Thread.sleep(5000);
        //仅在此改变missedIt的值
        missedIt = true;
        print("in workMethod() - just set missedIt=" + missedIt);
        print("in workMethod() - about to sleep for 3 seconds");
        Thread.sleep(3000);
        print("leaving workMethod()");
    }
/*
*该方法的功能是在要打印的msg信息前打印出程序执行到此所化去的时间,以及打印msg的代码所在的线程
*/
    private void print(String msg) {
        //使用java.text包的功能,可以简化这个方法,但是这里没有利用这一点
        long interval = System.currentTimeMillis() - creationTime;
        String tmpStr = "    " + ( interval / 1000.0 ) + "000";    
        int pos = tmpStr.indexOf(".");
        String secStr = tmpStr.substring(pos - 2, pos + 4);
        String nameStr = "        " + Thread.currentThread().getName();
        nameStr = nameStr.substring(nameStr.length() - 8, nameStr.length());   
        System.out.println(secStr + " " + nameStr + ": " + msg);
    }
    public static void main(String[] args) {
        try {
            //通过该构造函数可以获取实时时钟的当前时间
            Volatile vol = new Volatile();
            //稍停100ms,以让实时时钟稍稍超前获取时间,使print()中创建的消息打印的时间值大于0
            Thread.sleep(100); 
            Thread t = new Thread(vol);
            t.start();
            //休眠100ms,让刚刚启动的线程有时间运行
            Thread.sleep(100); 
            //workMethod方法在main线程中运行
            vol.workMethod();
        } catch ( InterruptedException x ) {
            System.err.println("one of the sleeps was interrupted");
        }
    }
}

按照以上的理论来分析,由于value变量不是volatile的,因此它在main线程中的改变不会被Thread-0线程(在main线程中新开启的线程)马上看到,因此Thread-0线程中的while循环不会直接退出,它会继续判断missedIt的值,由于missedIt是volatile的,当main线程中改变了missedIt时,Thread-0线程会立即看到该变化,那么if语句中的代码便得到了执行的机会,由于此时Thread-0依然没有看到value值的变化,因此,currValue的值为10,继续向下执行,进入同步代码块,因为进入前后要将该线程内的变量值与共享内存中的原始值对比,进行校准,因此离开同步代码块后,Thread-0便会察觉到value的值变为了50,那么后面的valueAfterSync的值便为50,最后从break跳出循环,结束Thread-0线程。

意料之外的问题

但实际的执行结果如下:

从结果中可以看出,Thread-0线程并没有进入while循环,说明Thread-0线程在value的值发生变化后,missedIt的值发生变化前,便察觉到了value值的变化,从而退出了while循环。这与理论上的分析不符,我便尝试注释掉value值发生改变与missedIt值发生改变之间的线程休眠代码Thread.sleep(5000),以确保Thread-0线程在missedIt的值发生改变前,没有时间察觉到value值的变化。但执行的结果与上面大同小异(可能有一两行顺序不同,但依然不会打印出if语句中的输出信息)。

问题分析

在JDK1.7~JDK1.3之间的版本上输出结果与上面基本大同小异,只有在JDK1.2上才得到了预期的结果,即Thread-0线程中的while循环是从if语句中退出的,这说明Thread-0线程没有及时察觉到value值的变化。

这里需要注意:volatile是针对JIT带来的优化,因此JDK1.2以前的版本基本不用考虑,另外,在JDK1.3.1开始,开始运用HotSpot虚拟机,用来代替JIT。因此,是不是HotSpot的问题呢?这里需要再补充一点:

JIT或HotSpot编译器在server模式和client模式编译不同,server模式为了使线程运行更快,如果其中一个线程更改了变量boolean flag 的值,那么另外一个线程会看不到,因为另外一个线程为了使得运行更快所以从寄存器或者本地cache中取值,而不是从内存中取值,那么使用volatile后,就告诉不论是什么线程,被volatile修饰的变量都要从内存中取值。《内存栅栏》

但看了这个帖子http://segmentfault.com/q/1010000000147713(也有人遇到同样的问题了)说,尝试了HotSpot的server和client两种模式,以及JDK1.3的classic,都没有效果,只有JDK1.2才能得到预期的结果。

哎!看来自己知识还是比较匮乏,看了下网友给出的答案,对于非volatile修饰的变量,尽管jvm的优化,会导致变量的可见性问题,但这种可见性的问题也只是在短时间内高并发的情况下发生,CPU执行时会很快刷新Cache,一般的情况下很难出现,而且出现这种问题是不可预测的,与jvm, 机器配置环境等都有关。

姑且先这么理解吧!一点点积累。。。

正确的分析在这里:http://blog.csdn.net/ns_code/article/details/17382679

这里附上分析结果时参考的帖子及文章

http://segmentfault.com/q/1010000000147713

http://www.iteye.com/problems/98213

http://www.oldcaptain.cc/articles/2013/08/21/1377092100971.html

from: http://www.importnew.com/20566.html

原文出处: 兰亭风雨

Java并发编程(5):volatile变量修饰符—意料之外的问题(含代码)相关推荐

  1. 【Java并发编程:volatile关键字之解析】

    Java并发编程:volatile关键字解析 - Matrix海子 - 博客园 在Java 5之前,volatile是一个备受争议的关键字:因为在程序中使用它往往会导致出人意料的结果.在Java 5之 ...

  2. Java并发编程:volatile关键字解析(转载)

    转自https://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 Java并发编程:volatile关键字解析 v ...

  3. 转载:Java并发编程:volatile关键字解析

    看到一篇写的很细致的文章,感谢作者 作者:Matrix海子 出处:http://www.cnblogs.com/dolphin0520/ 本博客中未标明转载的文章归作者Matrix海子和博客园共有,欢 ...

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

    文章目录 volatile关键字作用详解 原子/可见/有序 happen-before原则 volatile的作用 volatile的原理 volatile关键字作用详解 讲到Java中的volati ...

  5. Java并发编程实战——volatile

    引言 Java 语言提供了一种弱同步机制--volatile 变量.它的作用是确保将变量的更新操作通知到其他线程. 当把变量声明为volatile后,编译器和运行时都会注意到这个变量是共享的,因此不会 ...

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

     volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字 ...

  7. Java并发编程中volatile实现过程详细解析

    2019独角兽企业重金招聘Python工程师标准>>> 首先并发编程有三大特性: 可见性,有序性,原子性.volatile关键字实现了前面两个特性.那么它是如何实现这两个特性的呢? ...

  8. Java实战应用50篇(一)-Java并发编程:volatile关键字解析

    前言 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字 ...

  9. Java并发编程之volatile变量

    volatile提供了弱同步机制,用来确保将变量更新通知到其它线程.volatile变量不会被缓存在寄存器中或者对其它处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值.可以想象 ...

最新文章

  1. CentOS RPM安装MySQL 5.6修改默认密码
  2. 编写高质量JavaScript代码的基本技巧
  3. 抓包工具Charles简单使用介绍(可抓取Android中app的请求)
  4. 红旗桌面版本最新运用要领和成果解答100例-7
  5. phpstudy2018 安装xdebug扩展
  6. js模版引擎handlebars.js实用教程——with-终极this应用
  7. 图像分割-二阶导数零交叉点的含义
  8. Java 静态变量和静态方法
  9. 设计干货素材|UI设计中的插画模板,便于应用的好素材!
  10. 华为主导 5G 入网之争?
  11. web自动化测试python+selenium学习总结----selenium安装、浏览器驱动下载
  12. php留言板验证验证码,留言板7 图形验证码
  13. 奥维怎么记录沿线轨迹_奥维互动地图怎么绘制路线
  14. 一个很简单很简单的静态网页(附源代码)HTML+CSS
  15. 【latex论文】IEEE论文模板的使用教程
  16. python分词、词频统计以及根据词频绘制词云
  17. FDDB数据集标注文件:椭圆转换矩形
  18. photoshop---压缩图片大小/给人物换衣服
  19. R语言ggplot2可视化在轴标签中添加上标(Superscript)和下标(subscript)实战
  20. setex php,python redis setex可以设value为list或者其他数据结构吗?

热门文章

  1. 【未来可能用到】关于模型的100个问答-part2
  2. TensorFlow损失函数(loss function) 2017-08-14 11:32 125人阅读 评论(0) 收藏 举报 分类: 深度学习及TensorFlow实现(10) 版权声明:
  3. 关于数据科学,书上不曾提及的三点经验
  4. 信用卡葵花宝典笔记(一)
  5. Java Review - 并发编程_原子操作类原理剖析
  6. Elasticsearch-03 CentOS7 / Windows上部署Elasticsearch5.6.16集群模式
  7. Spring Cloud【Finchley】-16 Zuul的路由配置
  8. Oracle-内存管理解读
  9. Imageloader8-压缩图片
  10. 计算机导论第一章试题及答案,计算机导论第一章试题