GOF23种设计模式——单例模式详解,涵盖饿汉、懒汉、双重检测以及枚举等方案,一篇文章帮你搞定!!!

发布时间:2024年01月05日

单例模式介绍

介绍

一、单例模式
所谓的单例模式,就是采取一定的方法,保证在整个的软件系统中,针对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(如:getInstance()方法)

二、单例模式注意事项和细节说明
1、单例模式保证了系统内存中只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高性能
2、当想实例化一个单例类的时候,必须记住要使用相应的获取对象的方法,而不是使用new关键字
3、单例模式的使用场景:
需要频繁进行创建和销毁的对象、创建对象时耗时过多或者耗费资源过多(即:重量级对象),但经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

Spring中单例模式singleton

表示创建出来的对象,全局唯一,所有关于这个对象的操作,都用这个对象,在配置文件用scope关键字来设置
测试:
1、配置文件配置

<bean id="student" class="com.guohui.pojo.Student" c:name="李四" c:address="河北" scope="singleton"/>
</beans>

2、测试

 @Test
    public void TestPC(){
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student1 = context.getBean("student", Student.class);
        Student student2 = context.getBean("student", Student.class);

        System.out.println(student1 == student2);
    }
}

3、控制台

饿汉式

饿汉式(静态常量)

一、理解什么是饿汉式
饿汉式单例模式就是会在创建对象的时候,会把类中的对象都创建出来,有可能会造成内存的浪费

二、手写饿汉式单例模式
原理是通过私有化构造器,外在的方法是无法创建此类对象的,那么每次调用getInstance方法,都会返回这个类中自己创建的同一个类,保证了创建对象的唯一性

//饿汉式单例
public class Hungry {

    //饿汉式会在创建对象的时候把下面的对象都创建,可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024]; 
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    //私有化构造器,这样别人就无法创建这个单例对象了
    private Hungry(){

    }
    //在单例模式的类中创建这个对象,这样就能保证单例的类是全局唯一的了
    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

调用的时候直接通过对象.静态方法即可获得这个全局唯一的对象实例。通过验证可知,假设我们调用两次这个方法,那么比较两个方法创建对象的哈希值是相同的

三、饿汉式(静态常量)方法的优缺点
1、优点
这种写法比较简单,他是在类装载的时候就完成了实例化,避免了线程同步问题
2、缺点
在类装载的时候完成实例化,没有达到懒加载(懒加载,即我用到某个对象,再加载或者创建)的效果,如果从开始至终,从来没有使用这个实例,就会造成内存资源的浪费

饿汉式(静态代码块)

一、静态代码单例模式块写法

这种方式的调用也是和静态常量的方法一致,通过类名.getInstance方法来获取唯一的对象实例

二、优缺点
这种方式的优缺点和静态常量的优缺点是一致的

懒汉式

懒汉式(线程不安全型)

一、线程不安全的懒汉式

二、优缺点
1、优点
确实起到了懒加载的效果,但是只能在单线程下使用
2、缺点
如果在多线程下,一个线程进入到if(instance == null)判断语句块还未来得及往下执行,另一线程也通过了这个判断语句,便会产生多个实例。所以在多线程的情况下不可以使用这种方式

三、结论
实际开发中,不要使用这种方式!

懒汉式(线程安全,同步方法)

一、线程安全的懒汉式

这种方式确实能解决线程不安全的问题,当有多个线程执行这个getInstance方法的时候,后面的线程就会排队,等待释放锁再执行这个方法

主线程中运行这个方法,确实可以得到唯一的对象实例

二、优缺点
1、优点
解决了线程不安全的问题
2、缺点
效率太低了,每个线程想获得类的实例的时候,执行getInstance()方法都要进行同步。而实际上这个方法只执行一次实例化代码就够了,后面的想要获得这个对象实例直接通过return就行了,方法进行同步效率太低了

三、结论
实际开发中,不推荐使用这种方式

懒汉式(线程安全,同步代码块)

一、同步代码块方式

二、优缺点

双重检查(推荐使用)

双重检查

一、理解什么是懒汉式
所谓懒汉式就是上来先不创建对象,如果我要用这个对象,再进行创建,实现懒加载!

二、手写DCL懒汉式

//懒汉式单例模式
public class LazyMan {
    //私有构造器
    private LazyMan(){
        System.out.println(Thread.currentThread().getName() + "创建了LazyMan对象");
    }
    //加上volatile关键字,避免指令重排
    private volatile static LazyMan lazyMan;

    //双重检测锁模式的懒汉式  DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan == null){
            synchronized (LazyMan.class){ //这里直接用synchronized锁类的模板,全局唯一
                if (lazyMan == null){
                    lazyMan = new LazyMan();
                    /*创建对象的过程并不是原子性的,存在以下的步骤
                    * 1、分配内存空间
                    * 2、执行构造方法,初始化对象
                    * 3、把这个对象指向这个空间
                    * 假设存在线程A和B
                    * 此时就会存在一个指令重排的现象,如果A正常执行期望的123,是没有问题的
                    * 如果线程A执行了132,线程B再执行的时候会直接认为lazyMan!=null,此时的lazyMan还没完成构造,空间是虚无的
                    * 因此,必须要使lazyMan对象避免指令重排,在定义它的时候加上volatile关键字!!!
                    * */
                }
            }							
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                getInstance(); //此时控制台就会只打印构造器中的一条sout语句,并且只能创建1个对象
            }).start();
        }
    }
}

二、优缺点
1、优点
(1)Double-Check双重检查是多线程中经常用到的,如代码中所示,我们进行了两次的if(lazyMan == null)检查,这样就可以保证线程安全了
(2)这样,实例化代码只用执行一次,后面再次访问的时候,通过if判断就会直接return实例化对象,也避免了反复进行方法的同步
(3)终于实现了线程安全、懒加载、高效率

三、结论
在实际开发中,推荐使用这种单例设计模式

静态内部类

Holder静态内部类单例模式

一、静态内部类单例模式
手写Holder静态内部类单例模式

//Holder静态内部类单例模式
public class Holder {
    //凡是单例模式,都要先私有化构造器
    private Holder(){}

    //创建getInstance获取单例类的方法,里面调用内部类,创建Holder类对象
    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    
    //创建静态内部类
    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

实现原理:
当外部的Holder类装载的时候,静态内部类不会跟着装载,这样就实现了懒加载的情况;
当内部类开始装载的时候,首先要了解类在装载的时候是线程安全的,所以这种设计模式也保证了线程的安全

二、优缺点

枚举

枚举单例模式

一、代码实现

二、优缺点

至此,关于设计模式中的单例模式介绍完毕,内容比较多,建议收藏反复学习,后续还会持续更新,敬请期待~~~

文章来源:https://blog.csdn.net/CNpeaceful/article/details/135368209
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。