设计模式之单例模式

个人主页不漫游-CSDN博客

目录

知识铺垫

饿汉模式

懒汉模式

懒汉模式的线程安全问题

加锁

重复加锁对性能的影响

指令重排序问题


知识铺垫

什么是设计模式?

通俗点讲就类似于"棋谱",有一些固定的套路,可以针对一些特殊场景给出比较好的解决方案。

而在开发过程中,便可以起到模版的作用。

什么是单例模式?

单例模式能保证某个类在程序中只存在唯⼀⼀份实例,⽽不会创建出多个实例。

⽐如JDBC中的DataSource实例就只需要⼀个。--->http://t.csdnimg.cn/IQ3cj

主流的单例模式有以下两种写法--->

饿汉模式,顾名思义,就像一个总是急着吃东西的"饿汉"一样,不管是不是马上需要用到它,就是提前创建好单例实例。
懒汉模式则是延迟创建单例实例,直到真的需要用到它的时候才去创建,就像一个总是等到最后一刻才行动的"懒汉"一样。

举个例子,每次吃完饭后,对于洗碗这件事,有些人就立刻把碗给洗干净。(饿汉模式)

另外有些人则是放水里泡着,等到下一顿饭需要碗装菜的时候再洗。要装几个菜才洗几个碗。(懒汉模式)

饿汉模式

class Singleton {
    //在类加载时就创建单例实例
    private static final Singleton instance = new Singleton();

    //提供一个公共的访问方法,返回单例实例
    public static Singleton getInstance() {
        return instance;
    }

    //私有构造方法,防止外部实例化
    private Singleton() {
    }
}

仔细观察,instance成员变量就是"类成员",一开始就创建好,虽然还没人用,并且讲构造函数设为私有,只能通过访问getInstance()方法去获取instance。

这也就体现了单例模式的特点---确保不会随意new一个其他对象出来。

而且通过以下代码验证:单例模式确保了在整个应用程序中只有一个实例存在。

public class demo21 {
    public static void main(String[] args) {
        //获取单例实例
        Singleton dog1 = Singleton.getInstance();
        Singleton dog2 = Singleton.getInstance();

        //检查两个实例是否相同
        if (dog1 == dog2) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

因此无论调用多少次 ​getInstance​方法,返回的都是同一个实例,所以结果如图:

懒汉模式

class LazySingleton {
    //声明一个静态的实例变量,但不立即创建实例
    private static LazySingleton instance;

    //私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    //提供一个公共的静态方法,返回单例实例
    public static LazySingleton getInstance() {
        //如果实例还未创建,则创建它
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

和饿汉相比,懒汉模式需要用到的时候才会创建实例,即推迟了实例的创建。

所以不会一开始就new。只有在需要访问instance 的时候,才会创建实例。

其他和饿汉大同小异。加以验证。

public class demo22 {
    public static void main(String[] args) {

        SingleLazy dog1 = SingleLazy.getInstance();
        SingleLazy dog2 = SingleLazy.getInstance();

        if (dog1 == dog2) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

懒汉模式的线程安全问题

这里只谈论懒汉模式,因为饿汉模式不存在线程安全问题---没有涉及到线程的切换。

忘记了线程安全问题的相关知识点可以回顾一下-->http://t.csdnimg.cn/QC4er

仔细观察懒汉模式下的代码。

class LazySingleton {
    //声明一个静态的实例变量,但不立即创建实例
    private static LazySingleton instance;

    //私有构造方法,防止外部实例化
    private LazySingleton() {
    }

    //提供一个公共的静态方法,返回单例实例
    public static LazySingleton getInstance() {
        //如果实例还未创建,则创建它
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

这里有个逻辑是先判断再创建实例,但这两者之间可能会涉及到线程的切换,从而引发问题。

比如有2个线程t1,t2.

首先有一点是线程是随机调度,抢占式执行的。

在t1线程执行判断语句的时候, 就可能跳到t2线程,也符合条件,创建了一个实例。

但是回到t1线程也会创建一个新的实例(符合判断条件),所以就会覆盖第一次的实例。而原本的实例就会被回收掉了。

加锁

原因可以总结成if和new之间出现了线程切换。因此可以把它们打包成一个整体。

    public static SingleLazy getInstance() {
 
            synchronized (lock1) {
                //判断是否创建实例对象
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
     
        return instance;
    }

重复加锁对性能的影响

但也有新的问题,线程是安全了,但 instance一旦创建好之后,if语句不再执行。

但每次调用getInstance()去访问的时候,都会触发加锁操作。这样便可能引起堵塞,影响性能。

即还要判断一下要加锁的时候加锁,可以不用加锁的时候就不加锁。如图:

    public static SingleLazy getInstance() {
        //判断是否加锁
        if (instance == null) {
            synchronized (lock1) {
                //判断是否创建实例对象
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }

看到这里是不是很懵?怎么会有两个一模一样的if(instance==null)?

但只是写法相同,实际作用则不同。

**第一个if语句判断是否要加锁。**因为刚刚分析过,已经创建好实例的话就没必要次次调用getInstance()去访问的时候,都触发加锁操作。

**第二个if 语句则是判断是否要创建实例对象。**这个就是最开始的if语句。

指令重排序问题

到这里看似没有问题了,但想想创建一个实例对象需要几步?

1.分配内存空间。
2.初始化对象。
3.将引用指向分配的内存空间。

举个栗子~

想象你正在准备开一家新店。这个过程可以分为三个主要步骤:

  1. 选址:这就像是在内存中为你的对象"分配空间"。你找到了一个合适的地方。

  2. 装修:这就像是"初始化对象"。你开始装修店铺。

  3. 开门营业:这就像是"将引用指向分配的内存空间"。你完成了所有的准备工作,现在人们可以通过地址找到你的店铺,进来购物。在编程中,这个"地址"就是对象的引用,告诉程序去哪里找到这个对象。

但在多线程环境中,顺序可能发生调换,可能是1 3 2 的执行顺序。这样一来,又可能导致线程切换的问题。

所以安全起见,还要加上volatile关键字。

class SingleLazy {
    private static final Object lock1=new Object();

    //使用volatile关键字
    private static volatile SingleLazy instance;

    //私有构造函数,防止外部实例化
    private SingleLazy() {}

    public static SingleLazy getInstance() {
        //判断是否加锁
        if (instance == null) {
            synchronized (lock1) {
                //判断是否创建实例对象
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }
}

同样加以验证

public class demo22 {
    public static void main(String[] args) {

        SingleLazy dog1 = SingleLazy.getInstance();
        SingleLazy dog2 = SingleLazy.getInstance();

        if (dog1 == dog2) {
            System.out.println("1");
        } else {
            System.out.println("2");
        }
    }
}

看到最后,如果觉得文章写得还不错,++希望可以给我点个小小的赞++,您的支持是我更新的最大动力

相关推荐
gentle_ice8 分钟前
leetcode——矩阵置零(java)
java·算法·leetcode·矩阵
whisperrr.1 小时前
【JavaWeb06】Tomcat基础入门:架构理解与基本配置指南
java·架构·tomcat
火烧屁屁啦2 小时前
【JavaEE进阶】应用分层
java·前端·java-ee
m0_748257462 小时前
鸿蒙NEXT(五):鸿蒙版React Native架构浅析
java
我没想到原来他们都是一堆坏人2 小时前
2023年版本IDEA复制项目并修改端口号和运行内存
java·ide·intellij-idea
博一波3 小时前
【设计模式-行为型】迭代器模式
设计模式·迭代器模式
Suwg2094 小时前
【由浅入深认识Maven】第1部分 maven简介与核心概念
java·maven
花心蝴蝶.4 小时前
Spring MVC 综合案例
java·后端·spring
组合缺一6 小时前
Solon Cloud Gateway 开发:Helloword
java·gateway·solon
奕辰杰9 小时前
关于使用微服务的注意要点总结
java·微服务·架构