• 设计模式之单例模式


    1.前言

    所谓单例模式就是保证一个类只有一个实例并提供一个访问它的方法

    我们常常在整个系统中只需要一个对象实例的情况下使用单例模式。

    SpringBoot中,通过@Controller@Service注解注入的对象都是默认单例的。

    单例模式除了可以保证唯一的实例外,还可以严格的控制用户怎么访问它、何时访问它。简单来说就是对唯一实例的受控访问

    而根据实例的生成时间可以将单例模式分为饿汉式懒汉式

    2.懒汉式

    所谓懒汉式,就是延迟加载实例。

    接下来我们先用一段的代码向大家展示什么是懒汉式。

    public class SingletonLazy {
        // 默认为空,等到真正要获取实例的时候再生成
        private static SingletonLazy instance;
        // 构造方法私有,禁止使用构造方法生成实例对象
        private SingletonLazy(){};
        // 通过getInstance()获取唯一实例
        public SingletonLazy getInstance(){
            if(instance == null)
                instance = new SingletonLazy();
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这样的代码在单线程下当然是可通的,但是如果是多线程的情况下,可能会生成多个实例。

    所以通常情况下我们会对getInstance()方法加锁,避免生成多个实例对象。

    public class SingletonLazy {
        
        private static SingletonLazy instance;
        // 创建一个辅助资源类对象
        private static Object syncRoot = new Object();
        
        private SingletonLazy(){};
    
        public SingletonLazy getInstance(){
            // 对生成实例对象的代码加锁
    		synchronized (syncRoot) {
                if (instance == null)
                    instance = new SingletonLazy();
            }
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    但是仅仅这样做,还不够。

    在多线程情况下,如果某一个线程进入了synchronized代码块,它倒是爽了,但是其他线程都要在这里苦苦等待,执行效率比较低。

    所以在此我们引入了双重锁定(Double-Check Lock)。

    public class SingletonLazy {
        
        // 这里注意,使用volatile关键字是为了避免指令重排
        // 比如说instance = new SingletonLazy()这一行代码,实际上是分为三个步骤执行的
        // (1)为instance分配内存空间
        // (2)初始化instance
        // (3)将instance指向分配的内存地址
        // 如果指令重排以后,可能发生某个线程调用getInstance()方法引用以后未初始化的instance对象
        
        private volatile static SingletonLazy instance;
        
        private SingletonLazy(){};
    
        public SingletonLazy getInstance(){
        	// 第一道锁
        	if(intance == null){
        	// 第二道锁
    			synchronized (SingletonLazy.class) {
                	if (instance == null)
                    	instance = new SingletonLazy();
            	}
        	}
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    双重锁定以后,线程判断instance不为null以后,就会直接返回实例,不会再苦苦等待。

    当然你也可能有点懵,为什么要加两个判断instance是否为null。

    之所以这样做,是因为,如果一开始instance没有被实例化,然后两个线程执行该方法,他们都通过了第一道锁,如果没有第二个判断,那么两个线程最后会依次生成实例对象,这样就破化了单例模式。所以第二道锁中还需要在判断一次instance是否为空。

    3.饿汉式

    所谓饿汉式就是静态初始化,就是说当类被加载的时候就将自己实例化。

    public class SingletonHungry {
    
        // 当类加载的时候就直接实例化对象
        private static SingletonHungry instance = new SingletonHungry();
        private SingletonHungry(){}
    
        public static SingletonHungry getInstance(){
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    4.懒汉式与饿汉式

    由于饿汉式,即静态初始化的方式,它是类一加载就实例化的 对象,所以要提前占用系统资源。然而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全。所以到底使用哪一种方式,取决于实际的需求。

    通常情况下:

    • 如果对象内存占用不大,且创建不复杂,直接使用饿汉的方式即可
    • 其他情况均采用懒汉方式(优选)
  • 相关阅读:
    servlet如何获取PUT和DELETE请求的参数
    ISO9001体系咨询需要准备什么?
    Postgresql 重装
    【老生谈算法】matlab实现粒子群算法源码——粒子群算法
    这也能造成故障?我只给DTO类加了一个属性
    Python基础入门例程6-NP6 牛牛的小数输出
    程序环境和预处理
    计算机毕业设计Java的电影社区网站(源码+系统+mysql数据库+lw文档)
    【JavaSE】类和对象——下
    ctfshow MengXIn misc1
  • 原文地址:https://blog.csdn.net/qq_52002412/article/details/127986244