java设计模式——单例模式(一)

一. 定义与类型

定义:保证一个类仅有一个实例,并提供一个全局访问点

类型:创建型

二. 适用场景

想确保任何情况下都绝对只用一个实例

三. 优缺点

优点:

在内存里只有一个实例,减少了内存开销

可以避免对资源的多重占用

设置全局访问点,严格控制访问

缺点:

没有接口,扩展困难

四. 重点

私有构造器

线程安全

延迟加载

序列化和反序列化安全

反射

实用技能:反编译,内存原理,多线程Debug

五. 相关设计模式

单例模式和工厂模式

单例模式和享元模式

六. Coding

懒汉式:

/**
 * @program: designModel
 * @description: 懒汉单例,懒汉式注重的就是延迟加载,当在使用到这个实例的时候才会初始化
 * @author: YuKai Fan
 * @create: 2018-12-04 14:04
 **/
public class LazySingleton {
    private static LazySingleton lazySingleton = null;
    private LazySingleton() {

    }
    //如果只有这种方法,是线程不安全的。
    //在多线程环境下,使用这个方法,会有概率的产生不止一个实例的情况,虽然最后返回的还是同样的独享
    /*public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }*/

    //在方法上加锁,让这个方法每次只能有一个线程访问
    /*public synchronized static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }*/

    //在代码块上加锁,让这个方法每次只能有一个线程访问,这样只会产生一个实例
    //这种方式,锁的是class类,存在加锁和解锁的开销,对性能有一定影响
    public static LazySingleton getInstance() {
        synchronized(LazySingleton.class) {
            if (lazySingleton == null) {
                lazySingleton = new LazySingleton();
            }
        }
        return lazySingleton;
    }
}

如上方代码注释所说,懒汉式的注重点是延时加载,但是在性能和安全方面都所有影响,所以引出下面的双重检查模式

/**
 * @program: designModel
 * @description: 懒汉式双重检查。既符合延迟加载,也保证了安全性能
 * @author: YuKai Fan
 * @create: 2018-12-04 14:35
 **/
public class LazyDoubleCheckSingleton {
    //volatile关键字,使用解决了程序的重排序问题,即使在多线程的情况下,实例变量始终是最新的状态
    //使用这种方法是不让下面的2,3发生重排序
    private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
    private LazyDoubleCheckSingleton() {

    }
    public static LazyDoubleCheckSingleton getInstance() {
        if (lazyDoubleCheckSingleton == null) {
            synchronized (LazyDoubleCheckSingleton.class) {
                if (lazyDoubleCheckSingleton == null) {
                    //1.分配内存给这个对象
                    //2.初始化对象
                    //3.设置LazyDoubleCheckSingleton 只向刚分配的内存地址
                    //其中2和3的顺序可能会被颠倒,倒置,判断为null的时候,已经初始化对象,此时并不为空
                    //intra-thread semantics 不会改变单线程程序的重排序
                    lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
                }
            }
        }
        return lazyDoubleCheckSingleton;
    }
}

双重检查,顾名思义是对实例的是否创建做了两层的判断,例如,当thread0,进入getInstance(),此时lazyDoubleCheckSingleton==null,两层的判断都会过,当刚刚走到new LazyDoubleCheckSingleton()时,thread1进入方法时,第一次判断其实lazyDoubleCheckSingleton还是为null,所以会通过,但是由于后面代码加锁,所以无法进入,当thread0执行玩方法时,释放锁,让thread1拿到,在进行判断,此时lazyDoubleCheckSingleton != null,就会直接退出,这样保证了在多线程环境下,始终只有一个实例。

刚开始的时候,我一直认为这样多此一举,为什么要在加锁的外面在判断一次。后来思考明白了,thread0,thread1的执行顺序,以及执行方法的时机在真正的环境下其实是不知道的,由操作系统来决定的。所以有可能是在thread0已经拿到实例的时候,此时thread1才进入方法,这时候这个判断就会不成立,也就不会在获取锁和释放锁,也就提高了程序的性能。

从上面的代码注释可知,这种方式存在重排序的问题,上面解决重排序的问题是使用volatile关键字,来防止重排序,还有一种方式是可以重排序,但是thread1,不会看到。

/**
 * @program: designModel
 * @description: 使用静态内部类来防止多线程环境下对DoubleCheck的懒汉式单例模式的判断实例问题,同时也允许重排序
 * @author: YuKai Fan
 * @create: 2018-12-04 16:35
 **/
public class StaticInnerClassSingleton {
    //使用静态内部类,是基于class对象的初始化锁的延迟加载方式,在线程0执行方法时会去实例,即使发生重排序,线程1也会被锁在初始化阶段
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
    }
    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.staticInnerClassSingleton;
    }
    private StaticInnerClassSingleton() {

    }
}

饿汉式:

这是一个很简单的单例模式的方式,它与懒汉式的最大的区别就是延时加载。饿汉式单例模式是在程序加载的时候,就创建实例,但是如果不用该实例,就会占用资源

/**
 * @program: designModel
 * @description: 饿汉式,与懒汉式最大的区别,就是延时加载,但是饿汉式如果不用该实例,会占用资源
 * @author: YuKai Fan
 * @create: 2018-12-04 16:57
 **/
public class HungrySingleton implements Serializable {
    private final static HungrySingleton hungrySingleton;

    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {

    }
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }

    private Object readResolve() {
        return hungrySingleton;
    }
}

 

单线程的执行顺序

多线程的执行顺序

 

posted @ 2018-12-05 16:11  MichaelKai  阅读(150)  评论(0编辑  收藏  举报