一、前言

  JMM提供了volatile变量定义、final、synchronized块来保证可见性。

  用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。写了几个测试的例子,大家可以试一试。

  关于JMM,可参考:http://www.cnblogs.com/hujunzheng/p/5118256.html

二、主程序

  1. public class Main{
  2. public static void main(String[] args) throws InterruptedException{
  3. List<Thread> threadList = new ArrayList<Thread>();
  4. for(int i=0; i<10; ++i){
  5. Thread thread = new Thread(new Runnable() {
  6. @Override
  7. public void run() {
  8. Single.Holder.instance.add();
  9. }
  10. });
  11. threadList.add(thread);
  12. thread.start();
  13. }
  14.  
  15. for(Thread thread : threadList)
  16. thread.join();
  17.  
  18. System.out.println(Single.Holder.instance.x);
  19. }
  20. }

三、单例模式测试

  1、没有volatile,没有synchronized的情况   

  1. class Single{
  2. public int x = 0;
  3. public void add(){
  4. try {
  5. TimeUnit.MILLISECONDS.sleep(50);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. ++this.x;
  10. }
  11.  
  12. public static class Holder{
  13. public static Single instance = new Single();
  14. }
  15. }

    输出结果:8, 9, 10都出现过。可以多运行,多试一试,就会发现不同的结果。

  2、有volatile,没有synchronized

  1. class Single{
  2. public volatile int x = 0;
  3. public void add(){
  4. try {
  5. TimeUnit.MILLISECONDS.sleep(50);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. ++this.x;
  10. }
  11.  
  12. public static class Holder{
  13. public static Single instance = new Single();
  14. }
  15. }

    输出结果:最多出现的是9 和 10。

  3、没有volatile,有synchronized

  1. class Single{
  2. public int x = 0;
  3. public synchronized void add(){
  4. try {
  5. TimeUnit.MILLISECONDS.sleep(50);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. ++this.x;
  10. }
  11.  
  12. public static class Holder{
  13. public static Single instance = new Single();
  14. }
  15. }

  输出结果:无论运行多少次都是10。

四、关于volatile在DCL(double check lock)中的应用

  摘自:http://www.iteye.com/topic/260515 ,讲的不错。

  1. public class LazySingleton {
  2. private int someField;
  3.  
  4. private static LazySingleton instance;
  5.  
  6. private LazySingleton() {
  7. this.someField = new Random().nextInt(200)+1; // (1)
  8. }
  9.  
  10. public static LazySingleton getInstance() {
  11. if (instance == null) { // (2)
  12. synchronized(LazySingleton.class) { // (3)
  13. if (instance == null) { // (4)
  14. instance = new LazySingleton(); // (5)
  15. }
  16. }
  17. }
  18. return instance; // (6)
  19. }
  20.  
  21. public int getSomeField() {
  22. return this.someField; // (7)
  23. }
  24. }

  首先说明一下,为什么这种写法在java中是行不通的!

  假设线程Ⅰ是初次调用getInstance()方法,紧接着线程Ⅱ也调用了getInstance()方法和getSomeField()方法,我们要说明的是线程Ⅰ的语句(1)并不happen-before线程Ⅱ的语句(7)。线程Ⅱ在执行getInstance()方法的语句(2)时,由于对instance的访问并没有处于同步块中,因此线程Ⅱ可能观察到也可能观察不到线程Ⅰ在语句(5)时对instance的写入,也就是说instance的值可能为空也可能为非空。我们先假设instance的值非空,也就观察到了线程Ⅰ对instance的写入,这时线程Ⅱ就会执行语句(6)直接返回这个instance的值,然后对这个instance调用getSomeField()方法,该方法也是在没有任何同步情况被调用,因此整个线程Ⅱ的操作都是在没有同步的情况下调用 ,这说明线程Ⅰ的语句(1)和线程Ⅱ的语句(7)之间并不存在happen-before关系,这就意味着线程Ⅱ在执行语句(7)完全有可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值,这就是DCL的问题所在。很荒谬,是吧?DCL原本是为了逃避同步,它达到了这个目的,也正是因为如此,它最终受到惩罚,这样的程序存在严重的bug,虽然这种bug被发现的概率绝对比中彩票的概率还要低得多,而且是转瞬即逝,更可怕的是,即使发生了你也不会想到是DCL所引起的。

  我的理解是:线程I 和线程II 都有自己的工作存储,线程I 创建好了instance后,向内存刷新的时间是不确定的,所以线程Ⅱ在执行语句(7)完全有可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值。

  那么由于在java 5中多增加了一条happen-before规则:

  • 对volatile字段的写操作happen-before后续的对同一个字段的读操作。

  利用这条规则我们可以将instance声明为volatile,即: private volatile static LazySingleton instance;

   根据这条规则,我们可以得到,线程Ⅰ的语句(5) -> 语线程Ⅱ的句(2) (也就是线程),根据单线程规则,线程Ⅰ的语句(1) -> 线程Ⅰ的语句(5)和语线程Ⅱ的句(2) -> 语线程Ⅱ的句(7),再根据传递规则就有线程Ⅰ的语句(1) -> 语线程Ⅱ的句(7),这表示线程Ⅱ能够观察到线程Ⅰ在语句(1)时对someFiled的写入值,程序能够得到正确的行为。

  补充:在java5之前对final字段的同步语义和其它变量没有什么区别,在java5中,final变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正确运行了。

  就先写到这里了,如果大神路过,可以给我提供一个测试 volatile 的例子,我实在是写不出来。

java中volatile关键字的更多相关文章

  1. 【转】java中volatile关键字的含义

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

  2. 转:java中volatile关键字的含义

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

  3. Java中Volatile关键字详解 (转自郑州的文武)

    java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 一.基本概念 先补充一下概念:J ...

  4. Java中Volatile关键字详解

    一.基本概念 先补充一下概念:Java并发中的可见性与原子性 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值, ...

  5. java中volatile关键字的理解

    一.基本概念 Java 内存模型中的可见性.原子性和有序性.可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有 ...

  6. Java中Volatile关键字详解(转载)

    转载自:https://www.cnblogs.com/zhengbin/p/5654805.html 一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是 ...

  7. java中volatile关键字的含义

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  8. java中volatile关键字的含义 (转载)

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  9. 深入解析Java中volatile关键字的作用

    转(http://m.jb51.net/article/41185.htm)Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制 在java线 ...

  10. 关于Java中volatile关键字笔记

    volatile通常被认为是一种轻量级的synchronized,字面上它表示易变的,在并发编程中,它保证了共享变量的可见性.所谓可见性指的是,某个线程对变量进行操作后,其他线程能够读取到操作后的最新 ...

随机推荐

  1. Android -- PopupWindow(其中嵌套ListView 可以被点击)

    1. 效果图

  2. Unity3d:UI面板管理整合进ToLua

    本文基于 https://github.com/chiuan/TTUIFramework https://github.com/jarjin/LuaFramework_UGUI 进行的二次开发,Tha ...

  3. nginx android app 慢网络请求超时

    最近遇到了android 在慢网络下面请求服务器报 java.net.SocketException: recvfrom failed: ECONNRESET (Connection reset by ...

  4. HDU 2492 Ping pong (树状数组)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2492 Ping pong Problem Description N(3<=N<=2000 ...

  5. JQuery对象与DOM对象的区别与转换

      1.jQuery对象和DOM对象的区别 DOM对象,即是我们用传统的方法(javascript)获得的对象,jQuery对象即是用jQuery类库的选择器获得的对象; eg: var domObj ...

  6. webform 图片验证码制作

    界面:1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.as ...

  7. HOJ 1797 Red and Black

    传送门  http://acm.hit.edu.cn/hoj/problem/view?id=1797 总体的思路是遍历可以到达的' . ',将其对应的vis数组化为1,然后统计所有为1的vis项; ...

  8. java反射机制,通过类名获取对象,通过方法名和参数调

    import java.lang.reflect.Method;   import javax.persistence.Table; /**  * 通过注解javax.persistence.Tabl ...

  9. 安卓初級教程(5):TabHost的思考

    package com.myhost; import android.os.Bundle; import android.view.LayoutInflater; import android.wid ...

  10. Backbone源码分析(三)

    Backbone源码分析(一) Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨C ...