单例模式之饿汉式与懒汉式

一、什么是单例模式

单例模式是Java中最简单的设计模式之一,在单例模式中,保证一个类只能有唯一的一个实例对象。

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

二、饿汉模式

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

饿汉模式的主要特点是,该类的唯一对象在类加载的时候就被创建,且该实例对象在类的整个生命周期都存在。

优点:

  • 在类加载的时候就被创建,在多线程情况下也只会创建一个实例对象,是线程安全的。

缺点:

  • 如果这个对象不被使用,也会一直占据内存空间,导致资源的浪费。

三、懒汉模式

public class Singleton {
    private static Singleton instance;
    private Singleton(){}
    public static Singleton newInstance(){
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉模式可以在需要该实例对象的时候再进行创建,如果之前已经创建了该实例对象,则直接返回该对象。

优点:

  • 与饿汉模式相应,可以避免内存空间的浪费

缺点:

  • 懒汉模式不是线程安全的。在多线程并发执行的情况下,会导致多个实例对象被创建。

优化:

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

对懒汉模式的newInstance( )方法进行加锁,这样就避免了创建多个对象的问题。但是这样仍然存在问题,因为synchronized会对方法的执行效率产生影响,如果频繁调用newInstance( )方法,会导致程序运行效率的下降。所以采取DLC懒汉模式进行优化。

四、DLC懒汉模式

public class Singleton {
    private static Singleton instance;
    private Singleton(){}
    public static Singleton newInstance(){
        if (instance == null){//第一次校验
            synchronized(Singleton.class){
                if(instance == null){//第二次校验
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

DLC:Double-Lock-Check(双重校验锁)

采用双重校验锁,即使频繁调用newInstance方法,也只有在instance对象未被创建的时候才会加锁创建对象,而在大多数情况下直接返回已经创建过的对象。这就解决了synchronized锁对程序性能影响的问题。

虽然DLC解决了单例模式的懒加载、线程安全和性能提高,但是仍然存在隐患,这种隐患在JDK1.5进行了消除。

五、DLC懒汉模式存在的隐患

对象被创建的过程不是原子性的,它可以分为三个步骤:

  1. 开辟内存空间
  2. 执行构造方法,初始化对象
  3. 将开辟的内存空间地址赋值给instance字段

在JVM中又存在指令重排序的特点,单线程情况下,在不影响执行结果的前提下,JVM会对要执行的语句顺序进行优化,即指令重排序。在单线程情况下,指令重排序是没有什么问题的,但是在多线程情况下,指令重排序就会造成一定的问题。

在对象创建的过程中,对于A、B两个线程,如果发生了指令重排序,A线程执行语句的顺序为1->3->2,且A线程在执行2过程之前,B线程开始执行,这时就会产生问题。对于B线程,因为A线程已经将开辟空间的地址赋值给了instance字段,在DLC第一次校验 if(instance == null) 的时候,就会发现instance不等于null,然后直接返回了instance,而此时A线程还没有进行对象的初始化。

对这种情况下指令重排序的解决方案就是,加入volatile关键字,禁止指令重排序,也就是JDK1.5对DLC懒汉模式的优化:

public class Singleton {
    private static volatile Singleton instance;//加入volatile关键字禁止指令重排序
    private Singleton(){}
    public static Singleton newInstance(){
        if (instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

参考:

https://blog.csdn.net/fly910905/article/details/79286680

https://www.runoob.com/design-pattern/singleton-pattern.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值