设计模式之单例模式

个人主页不漫游-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");
        }
    }
}

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

相关推荐
lifallen2 分钟前
Paimon vs. HBase:全链路开销对比
java·大数据·数据结构·数据库·算法·flink·hbase
深栈解码41 分钟前
JMM深度解析(三) volatile实现机制详解
java·后端
liujing102329291 小时前
Day04_刷题niuke20250703
java·开发语言·算法
Brookty1 小时前
【MySQL】JDBC编程
java·数据库·后端·学习·mysql·jdbc
能工智人小辰1 小时前
二刷 苍穹外卖day10(含bug修改)
java·开发语言
DKPT1 小时前
Java设计模式之结构型模式(外观模式)介绍与说明
java·开发语言·笔记·学习·设计模式
缘来是庄1 小时前
设计模式之外观模式
java·设计模式·外观模式
你是橙子那我是谁1 小时前
设计模式之外观模式:简化复杂系统的优雅之道
设计模式
知其然亦知其所以然2 小时前
JVM社招面试题:队列和栈是什么?有什么区别?我在面试现场讲了个故事…
java·后端·面试
harmful_sheep2 小时前
Spring 为何需要三级缓存解决循环依赖,而不是二级缓存
java·spring·缓存