单例模式学习总结

2019-07-31  本文已影响0人  繁星追逐

单例模式:

1,静态的变量和静态的方法
2,私有化构造方法
volatile 关键字用于保持线程之间的可见性和防止指令重排序问题,主内存和工作内存交换时,保持原子操作的连续性,从而让变量始终看到的是自己的最新值。即线程操作的相互可见性,理解volatile作用之前我们先了解一下修改的过程
线程在jvm运行时内存分配过程:


image.png

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存
变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,
在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

竞态条件是指的是程序需要判断状态来做出操作,然后多线程中可能在完成操作之前就对状态作出了修改,这时候结果不是我们想要的,需要通过synchronized来同步。

比如单例模式中第一次新建时判断完状态,还未完成,另一个线程来按照空进行判断,产生两次创建。

但又出现串行化的单例,同步有比较耗时,所以采用双重检查锁,即在同步之前作一个非空判断

class Singleton {
    private static Singleton instance;
    private Singleton(){}
    public static Singleton getInstance() {
        if ( instance == null ) { //当instance不为null时,仍可能指向一个“被部分初始化的对象”
            synchronized (Singleton.class) {
                if ( instance == null ) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

“看起来”非常完美:既减少了阻塞,又避免了竞态条件。不错,但实际上仍然存在一个问题——当instance不为null时,仍可能指向一个被部分初始化的对象。
问题出在这行简单的赋值语句:

instance = new Singleton();

它并不是一个原子操作。事实上,它可以”抽象“为下面几条JVM指令:

memory = allocate();    //1:分配对象的内存空间
initInstance(memory);   //2:初始化对象
instance = memory;      //3:设置instance指向刚分配的内存地址

上面操作2依赖于操作1,但是操作3并不依赖于操作2,所以JVM可以以“优化”为目的对它们进行重排序,经过重排序后如下:

memory = allocate();    //1:分配对象的内存空间
instance = memory;      //3:设置instance指向刚分配的内存地址(此时对象还未初始化)
ctorInstance(memory);   //2:初始化对象

可以看到指令重排之后,操作 3 排在了操作 2 之前,即引用instance指向内存memory时,这段崭新的内存还没有初始化——即,引用instance指向了一个"被部分初始化的对象"。此时,如果另一个线程调用getInstance方法,由于instance已经指向了一块内存空间,从而if条件判为false,方法返回instance引用,用户得到了没有完成初始化的“半个”单例。
解决这个该问题,只需要将instance声明为volatile变量:

private static volatile Singleton instance;

对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。下面是基于保守策略的JMM内存屏障插入策略:

在每个volatile写操作的前面插入一个StoreStore屏障。

在每个volatile写操作的后面插入一个StoreLoad屏障。

在每个volatile读操作的后面插入一个LoadLoad屏障。

在每个volatile读操作的后面插入一个LoadStore屏障。

volatile和synchronized区别
1、volatile不会进行加锁操作:

volatile变量是一种稍弱的同步机制在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。

2、volatile变量作用类似于同步变量读写操作:

从内存可见性的角度看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。

3、volatile不如synchronized安全:

在代码中如果过度依赖volatile变量来控制状态的可见性,通常会比使用锁的代码更脆弱,也更难以理解。仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它。一般来说,用同步机制会更安全些。

4、volatile无法同时保证内存可见性和原则性:

加锁机制(即同步机制)既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性,原因是声明为volatile的简单变量如果当前值与该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。

最后回归正题:
三种安全的单例模式:
恶汉模式

/**
 * 单例模式,饿汉模式,不管为不为空,先直接new一个出来
 */
public class SingletonImp {
    // 饿汉模式
    private static SingletonImp singletonImp = new SingletonImp();
    // 私有化(private)该类的构造函数
    private SingletonImp() {
    }

    public static SingletonImp getInstance() {
        return singletonImp;
    }

    public static void main(String[] args) {
        System.out.println(SingletonImp.getInstance());
    }
}

双重检查锁:

/**
 * SingletonImp3中每次调用getInstance都会加同步锁,而加锁是一个很耗时的过程
 * 实际上加锁只需要在第一次创建对象时
 */
public class SingletonImp4 {
    private static SingletonImp4 singletonImp4;

    private SingletonImp4() {}

    public static SingletonImp4 getInstance() {
        // 第一次创建时才加锁
        if (singletonImp4 == null) {
            synchronized (SingletonImp4.class) {
                if (singletonImp4 == null) {
                    singletonImp4 = new SingletonImp4();
                }
            }
        }

        return singletonImp4;
    }
}
public class SingletonImpl7 {
    //用volatile保证线程可见性和指令重排序
    private static volatile SingletonImpl7 singletonImp;
    private SingletonImpl7(){
    }
    public static SingletonImpl7 getInstance(){
        if (null == singletonImp){
            synchronized(SingletonImpl7.class){
                if (null == singletonImp){
                    singletonImp = new SingletonImpl7();
                }
            }
        }
        return singletonImp;
    }
}

内部类模式:

/**
 * SingletonImp5使用静态代码块,如果该类中还有其他静态方法,调用后就会执行静态代码块使得对象过早创建
 * 使用一个静态类来创建Singleton,其他静态方法只要没有调用Nested.singletonImp6就不会创建Singleton
 * 实现了需要时才创建实例对象,避免过早创建
 */
public class SingletonImp6 {
    private SingletonImp6() {}

    // 专门用于创建Singleton的静态类,加载类时,同时执行静态块,给静态变量赋值
    private static class Nested{
        private static SingletonImp6 singletonImp6;
        static {
           singletonImp6 = new SingletonImp6();
        }
    }
    //直接返回静态内部类的静态变量,实现需要时在加载
    public static SingletonImp6 getInstance() {
        return Nested.singletonImp6;
    }
上一篇 下一篇

猜你喜欢

热点阅读