设计模式之单例设计模式

1、懒汉式单例模式 ,线程不安全(不推荐使用)

 private  static LazyMan instance;
    /**
     * 私有构造器避免外部new
     */
    private LazyMan() {
    }
    /**
     * 1、单线程单例模式,多线程不适合
     *
     * @return
     */
    public static LazyMan getInstance() {
        //此处若多线程同时调用会产生多个实例,仅适用于单线程情况下
        if (instance == null) {
            instance = new LazyMan();
        }
        return instance;
    }

2、懒汉式单例模式 ,线程安全(DCL双重锁检测机制)不完美!

    private  static LazyMan instance;
    public static LazyMan getDouLockInstance() {
        //第一次检测
        synchronized (LazyMan.class) {
            if (instance == null) { //第二次检测
                instance = new LazyMan();
            }
        }
        return instance;
    }

这段代码看起来很完美,很可惜,它是有问题。主要在于instance = new LazyMan()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情(new的时候jvm干了哪些事参阅JVM内存划分和new时JVM做了什么)。

1.给 instance 分配内存
2.调用 LazyMan 的构造函数来初始化成员变量
3.将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
个人理解:多线程时候多个线程都在自己的栈空间内对instance进行判断是否为空,实际上可能别的线程已经新建了对象,只是当前线程的instance副本并不是最新的,从而导致多次初始化异常。若是能够多线程在判断instance是不是为空时从主存拿最新数据就能完美了,所以解决方案是:
我们只需要将 instance 变量声明成 volatile 就可以了

   //多线程&完美的懒汉模式
   private volatile static LazyMan instance;
    public static LazyMan getDouLockInstance() {
        synchronized (LazyMan.class) {
            if (instance == null) {
                instance = new LazyMan();
            }
        }
        return instance;
    }

3、饿汉式 (线程安全,比较常用,但容易产生垃圾)

  private static HungryMan instance = new HungryMan();

    private HungryMan() {
    }
    public static HungryMan getInstance() {
        return instance;
    }

这种写法如果完美的话,就没必要在啰嗦那么多双检锁的问题了。缺点是它不是一种懒加载模式(lazy initialization),单例会在加载类后一开始就被初始化,即使客户端没有调用 getInstance()方法也会生成实例,造成一定的资源浪费。

4、静态内部类实现(线程安全+懒汉式=最佳推荐模式)

  private InnnerClass() {
    }
   //d对外公共提供实例的方法
    public static InnnerClass getInstance() {
        return InnerClassInner.instance;
    }
    //内部类是**私有的**,在其中实现new 实例,是懒汉式实现
    private static class InnerClassInner {
        private static InnnerClass instance = new InnnerClass();
    }

这种写法仍然使用JVM本身机制保证了线程安全问题;由于 InnerClassInner 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

5、枚举实现单例模式

public enum SingleEnum {
    INSTANCE;
}

认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上

枚举类隐藏了私有的构造器(String,int)。
枚举类的域 是相应类型的一个实例对象
但枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。

本站的文章多是老王开发工作中问题的记录,一个字一个字敲的,切实可行,可以分享,需要留个原文链接,至少也意思意思吧!
vsalw技术博客 » 设计模式之单例设计模式

每个人都是以自己独特的方式体味生活,或许别人不理解,但自己知道:其中的酸甜苦辣就叫做幸福!

认同! 瞎扯淡!