什么是单例模式(上)

Posted 豆豆的杂货铺

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是单例模式(上)相关的知识,希望对你有一定的参考价值。

1什么是单例模式?


单例模式是指在系统运行期间,一个类有且只有一个对象实例对外提供服务,并提供一个全局的访问入口。


2单例模式的应用场景有哪些?


数据库连接池或者线程池。

spring IOC 容器中的 bean 默认是单例模式。

Web 应用的配置对象,一个应用配置文件肯定是全局共享的,所以一般使用单例模式。

3单例模式的关键点

私有构造方法,不可以随便进行 new 操作。

提供全局访问入口,获取单例对象。

4单例模式的写法


饿汉式


public class Singleton {
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() { }
}

这段代码没什么好说的,类加载时直接初始化。因为类加载只加载一次,所以不存在线程安全问题。

懒汉式


public class Singleton {
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if(null == instance){
            instance = new Singleton();
        }
        return instance;
    }

    private Singleton() { }
}

他们的名字很形象,「饿汉式」主动寻找吃的,「懒汉式」等着别人喂。相比「饿汉式」而言,「懒汉式」是在 getInstance 时才去做类的初始化。但是这段代码这里存在一个问题,就是线程安全问题。

假设 getInstance 方法第一次被调用,instance 还是 null,线程 A 与线程 B 同时执行到 if 判断,因为 instance 还是 null,所以两个线程同时通过了 if 判断,执行 new 操作,最终 instance 被实例化两次。

线程安全的单例模式


public class Singleton {
    private static Singleton instance = null;

    public static Singleton getInstance() {
        synchronized (Singleton.class) {
            if (null == instance) {
                instance = new Singleton();
            }
        }
        return instance;
    }

    private Singleton() { }
}

为了防止 instance 被实例化多次,所以在 new 操作之前加 synchronized 锁住整个类。这样字就可以保证 new 操作只被执行一次了。为什么是先获取锁在做判空操作呢,你想下,如果先判空在获取锁跟「懒汉式」岂不是没啥区别。但是这段代码也不是完美的,因为只需要第一次执行 new 操作时加锁就可以了,后面就不需要锁了,因为有 if 判空操作。现在的代码每次 getInstance 时都获取锁很影响效率。

兼容效率与线程安全的单例模式


public class Singleton {
    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton4.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    private Singleton() {}
}

像这样两次判空的机制叫做双重检测机制。目前来看只有第一次会获取锁,提高了执行效率,而且也不会产生多个实例对象了,完美。但事实真的如此吗?不存在的,这段代码还有一个隐藏的 bug。

假设这样一个场景,两个线程先后进入 getInstance 方法,线程 A 正在构建对象,线程 B 刚进入方法。



这种情况看似没啥问题,要么对象没有构建完成,if 返回 true,要么对象构建完成,if 返回 false。事实真的是这样,不是的,这里涉及到 JVM 的指令重排。就拿简单的一句 nstance = new Singleton() 来说,JVM 会编译成如下指令:


什么是单例模式(上)


但是这些指令顺序并非一成不变,有可能会经过 JVM 和 CPU 的优化,指令重排成下面的顺序:


什么是单例模式(上)


当线程 A 执行完 1,3 ,时,instance 对象还未完成初始化,但已经不再指向 null。此时如果线程 B 抢占到 CPU 资源,执行 if(instance == null)的结果会是 false,从而返回一个没有初始化完成的 instance 对象。


说白了就是因为 new 操作不是原子操作才会出现这个 bug,那如何解决这个问题呢,只需要在 instance 对象前加上 volatile 关键字即可。


单例模式最终版


public class Singleton {
    private static volatile Singleton instance = null;

    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton4.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    private Singleton() { }
}


什么是 volatile 关键字

被 volatile 修饰的变量禁止指令重排,也就是说 instance = new Singleton() 的执行顺序始终是

如此,在线程 B 看来要么 instance 指向 null,要么初始化完成,不会出现中间状态,也就保证了安全。

其实 volatile 关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。也就是大家所说的可见性。


--END--





关注「豆豆先生的小屋」,发现更多原创内容。分享技术,畅谈人生,欢迎勾搭!


以上是关于什么是单例模式(上)的主要内容,如果未能解决你的问题,请参考以下文章

什么是单例设计模式

什么是单例模式?

设计模式之单例模式

java的单例模式怎么能保证始终是单例

什么是单例模式

什么是单例模式