单例设计模式

128 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

本文系作者 不太自律的程序猿原创,转载请私信并在文章开头附带作者和原文地址链接。

单例设计模式

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

场景:重量级的对象,不需要多个实例,如线程池,数据库连接池。

实现方式:

1.饿汉模式(先创对象)

2.懒汉模式(后创对象)

饿汉模式

饿汉模式是:直接创建实例对象,不管你是否需要这个对象都会去创建

public HungrySingleton{ 
        private static HungrySingleton instance = new HungrySingleton() ;//属性私有化 
        
        private HungrySingleton(){
            //构造函数私有化 
        } 
        
        public HungrySingleton getHungrySingleton(){
            //提供共有的方法 
            return instance; 
        } 
}

懒汉模式

懒汉模式是:类加载时没有创建实例,使用时才会去创建该对象,延迟加载

我们逐步的去实现并思考每次代码实现之后是否存在BUG。

不能实现单例
public LazySingleton{ 
    private LazySingleton instance;//属性私有化 
    
    private LazySingleton(){
    //构造函数私有化 
    } 
    
    public LazySingleton getLazySingleton(){
        //提供共有的方法 
        //判断lazySingleton为空的时候我们再去创建 
        if(instance==null){ 
            instance = new LazySingleton(); 
        } 
        return instance; 
    } 
}

现状:此时的代码在多线程中不能实现单例。

解决方案:所以我们可以将getLazySingleton()方法添加synchronized关键字修饰整个方法。但是这样性能会下降很多。

单线程安全的懒汉式
public LazySingleton{ 
    private LazySingleton instance;//属性私有化 
    
    private LazySingleton(){
        //构造函数私有化 
    } 
    
    public synchronized LazySingleton getLazySingleton(){//提供共有的方法 
        //判断lazySingleton为空的时候我们再去创建 
        if(instance==null){ 
            instance = new LazySingleton(); 
        } 
        return instance; 
    } 
}

现状:此时的代码在多线程中能够实现单例,但是因为getLazySingleton()方法添加synchronized关键字修饰,导致方法性能很低。

解决方案:将synchronized放到getLazySingleton方法内部,去修饰代码块,提升性能。

DCL双重检查锁机制 (DCL:double checked locking)
public LazySingleton{ 
    private LazySingleton instance;//属性私有化 
    
    private LazySingleton(){
        //构造函数私有化 
    } 
    
    public LazySingleton getLazySingleton(){//提供共有的方法 
        //判断lazySingleton为空的时候我们再去创建 
        if(instance==null){ 
            synchronized(LazySingleton.class){ 
                //多线程等待所释放的时候,如果不加判断,会创建多次,所以需要在加一次判断。 
                if(instance==null){ 
                    instance = new LazySingleton(); 
                    //字节码层 
                    //JIT ,CPU 
                    //1、分配空间
                    //2、初始化 
                    //3、引用赋值 

                    //如果指令重排序 
                    //1、分配空间 
                    //3、引用赋值 
                    // 执行到这的时候lazySingleton已经不为null,因为已经指向了对应的空间地址,那么另一个线程此时获取到的lazySingleton并没有实际的值 
                    //2、初始化 
                } 
            } 
        } 
        return instance; 
    } 
}

现状:此时的代码在多线程中能够实现单例,也通过synchronized修饰代码块的方式,双重判断提高了性能,但是在多CPU情况下内部还是存在指令重排序的问题。

解决方案:使用volatile关键字防止指令重排序。

最终版本:
public LazySingleton{ 
    private volatile LazySingleton instance;//属性私有化 
    
    private LazySingleton(){
        //构造函数私有化 
    } 
    
    public LazySingleton getLazySingleton(){//提供共有的方法 
        //判断lazySingleton为空的时候我们再去创建 
        if(instance==null){ 
            synchronized(LazySingleton.class){ 
                //多线程等待所释放的时候,如果不加判断,会创建多次,所以需要在加一次判断。 
                if(instance==null){ 
                    instance = new LazySingleton(); 
                } 
            } 
        } 
        return instance; 
    } 
}

感谢诸君的观看,文中如有纰漏,欢迎在评论区来交流。如果这篇文章帮助到了你,欢迎点赞👍关注。