• 单例模式中用volatile和synchronized来满足双重检查锁机制


    背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的。

    例子1:没有volatile修饰的uniqueInstance

    public class Singleton {
        private static Singleton uniqueInstance;
    
        private Singleton(){
        }
    
        public static Singleton getInstance(){
            if(uniqueInstance == null){ //#1
                synchronized(Singleton.class){ //#2
                    if(uniqueInstance == null){ //#3
                        uniqueInstance = new Singleton(); //#4
                        System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
                    } else {
                        System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
                    }
                }
            }
            return uniqueInstance;
        }
    }
     1 public class TestSingleton {
     2     public static void main(final String[] args) throws InterruptedException {
     3         for (int i = 1; i <= 100000; i++) {
     4             final Thread t1 = new Thread(new ThreadSingleton());
     5             t1.setName("thread" + i);
     6             t1.start();
     7         }
     8     }
     9 
    10     public static class ThreadSingleton implements Runnable {
    11         @Override
    12         public void run() {
    13             Singleton.getInstance();
    14         }
    15     }
    16 }

    这里面的结果有可能会是:(没有真正重现过,太难模拟了)

    1 thread2: uniqueInstance is initalized...
    2 thread3: uniqueInstance is initalized...
    Singleton被实例化两次了,和我们的单例模式设计期望值不一致:类永远只被实例化一次.

    原因分析:
    1. thread2进入#1, 这时子线程的uniqueInstance都是为空的,thread2让出CPU资源给thread3
    2. thread3进入#1, 这时子线程的uniqueInstance都是为空的, thread3让出CPO资源给thread2
    3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance。thread2执行完毕让出CPO资源给thread3
    4. thread3接着#1跑下去,跑到#3的时候,由于#1里面拿到的uniqueInstance还是空(并没有及时从thread2里面拿到最新的),所以thread3仍然会执行#4,#5.1
    5. 最后在thread2和thread3都实例化了uniqueInstance

    例子2:用volatile修饰的uniqueInstance

    这里就不贴重复的代码了,因为只是加多一个volatile来修饰成员变量:uniqueInstance,

    但是结果却是正确的了, 其中一个可能结果:

    thread2: uniqueInstance is initalized
    thread3: uniqueInstance is not null now...

    原因分析:

    volatile(java5):可以保证多线程下的可见性;

    读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。

    写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

    1. thread2进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2),thread2让出CPU资源给thread3
    2. thread3进入#1, 这时子线程的uniqueInstance都是为空的(java内存模型会从主线程拷贝一份uniqueInstance=null到子线程thread2), thread3让出CPO资源给thread2
    3. thread2会依次执行#2,#3,#4, #5.1,最终在thread2里面实例化了uniqueInstance(由于是volatile修饰的变量,会马上同步到主线程的变量去)。thread2执行完毕让出CPO资源给thread3
    4. thread3接着#1跑下去,跑到#3的时候,会又一次从主线程拷贝一份uniqueInstance!=null回来,所以thread3就直接跑到了#5.2
    5. 最后在thread3不再会重复实例化uniqueInstance了

    参考文章:如何在Java中使用双重检查锁实现单例

    官方文档说明

    深入理解Java内存模型(一)——基础

    双重检查锁定与延迟初始化

  • 相关阅读:
    jQuery EasyUI 详解
    The Google Test and Development Environment (持续更新)
    MTSC 2019 深圳站精彩议题第一波更新! | 七五折门票火热售票中
    MTSC2019-深圳站 议题征集
    [ZZ] [精彩盘点] TesterHome 社区 2018年 度精华帖
    MTSC2018 | 确认过眼神,在这里能遇见Google、阿里、百度......
    四岁啦 | 第四届中国移动互联网测试大会期待您的光临
    聊聊优酷上的那些车评
    聊聊Google DSM产品的发布
    尝试 Markdown 写测试用例
  • 原文地址:https://www.cnblogs.com/damonhuang/p/5431866.html
Copyright © 2020-2023  润新知