设计模式---单例模式

文章介绍了设计模式的重要性,特别是单例模式的概念,它确保类只有一个实例。详细阐述了饿汉模式和懒汉模式的实现,饿汉模式在类加载时即创建实例,而懒汉模式则在首次需要时创建。懒汉模式中,使用了双重检查锁定(DCL)来保证线程安全,同时避免不必要的同步开销。volatile关键字用于防止指令重排序带来的问题,确保多线程环境下的正确性。
摘要由CSDN通过智能技术生成

目录

目录

一.设计模式

二.单例模式

1.饿汉模式

2.懒汉模式


 

一.设计模式

        在软件开发中,常常会针对一些场景下遇到一些问题,对于这种问题场景,大佬程序员总结出了一些固定套路来解决这些问题,只要程序员采取了大佬们的套路来编写代码,即使程序员水平不够高,也可以很好地解决问题,降低了代码出现BUG的可能。

        设计模式就像魔方的公式一样,我们只要按照公式,即使被扭的非常乱,很难复原,但只要按照公式操作,只要你耐心一点,就能实现复原。


二.单例模式

        单例模式是设计模式之一,根据设计模式的名字可以看出它的特点,即在某个类在程序中只会存在唯一一个实例。在针对一些场景下,我们要求一个类只能存在一个实例。

        而单例模式的具体实现,有饿汉模式和懒汉模式两种。

1.饿汉模式

        该种单例模式会被称为饿汉模式的原因是实例一开始就会被创建,而不是等你需要获取的时候才创建,对这种略显急切的方式,称其更为形象的名字为饿汉。

代码:

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

    public Singleton getInstance(){
        return instance;
    }
    
    private Singleton(){}
}

第一、instance被static修饰,这样一来,它就变成了类属性,在类加载时被创建,且只会创建一次,其次就是static保证了它一定会被创建,如果是普通成员,它被创建一定要是外部new这个对象的时候,但是Singleton这个类的构造方法是私有的,外部无法new,这就尬住了。

第二、Singleton这个类,提供了getInstance方法,以供外部获得instance实例。

第三、Singleton这个类,它的构造方法是被private修饰,所以外部是无法直接new的,这也保证了Singleton实例不会被多次创建。


2.懒汉模式

        懒汉模式与饿汉模式很相似,它们实现单例的方式是一样的,差距就在懒汉模式为了对得起它的名字,将实例的创建放到了外部需要获取的时候。但懒汉这种行为是更加值得鼓励的,它只要需要使用的时候才创建,不会浪费资源。

上代码:

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

       可以看出,在外部没有调用getInstance之前,instance是为null的,在调用之后,才会调用构造方法,创建实例,这种你不要求它,它就永远不会行动的模式,用懒汉形容再合适不过。


        对比饿汉与懒汉的实现,不同的地方主要就在与getInstance中的代码。那分析这段代码,我们就先屏蔽掉第一个if和synchronized,就看剩下的if。

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

如果代码这样些,看着多顺眼。但是奈何多线程的“特性”呢,这势必要考虑线程安全的问题。

为何加锁?

在多个线程调用getInstance的时候,如果一个线程做完if判断之后,就被CPU切走,另一个线程也做了if判断,两次判断都成立,那么就发生了两次new实例的现象,这就BUG了,所以我们必须要在每次做if判断之前加上锁,这样才能保证线程的安全。


如下图:

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

那么,锁也加了,线程安全问题也解决了,怎么还多了个if呢?

        我们再think一下,这样的代码,线程每次调用getInstance都会上锁,然后判断,那如果instance已经被创建过了,但还是每次都会加锁,这就造成了不必要的开销,一个if判断语句的开销是大大小于加锁操作的。那么,在一开始先判断,就能避免加无意义的锁。

        在这里,这两个if代码是一样的,但是它们的作用确是截然不同。第一个if是为了判断要不要加锁,当instance已经被创建后,就不会有线程安全问题了,加锁就无意义了,这个if能大大减少开销;第二个if是判断instance是否要被创建。

        最后,instance还是被volatile修饰的,这里的具体原因也很简单:

每次在new新的对象的时候,会有三件事情的发生:

①分配一块内存空间;

②根据构造方法,在内存空间上构造出对象;

③把内存空间的地址赋值给instance引用。

        由于编译器存在指令重排序的情况,即上述三个行为可能是123的顺序,也可能是132的顺序,虽然在单线程情况下指令重排序也不会发生什么影响,但是在多线程环境下,如果new 按照132的顺序来执行,第一个线程执行完3后,将地址赋值给instance引用,但是这个对象还没有被构造完全,那么线程被CPU切走后,第二个线程走if语句时,发现instance非null,就直接返回了一个残缺的对象,这就导致了程序的BUG。所以我们加上volatile关键字,这样就可以保证在new这个对象的时候不会发生指令重排序。


以上就是本文的全部内容了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

todd1er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值