单例模式(线程安全)

目录

一 . 什么是单例模式 

    饿汉与懒汉模式介绍 : 

二 . 饿汉模式

三 . 懒汉模式


一 . 什么是单例模式 

单例模式 ---> 是一种经典的设计模式.(设计模式 : 类似于"棋谱" , java中的大佬们针对一些工作中常见的场景,总结出来的编写"套路").  

单例 : 指的是单个实例(对象) , 一个程序中, 某个类,只可以创建出一个实例(对象),不能创建多个对象.

Java中的单例模式 , 通过借助Java语法, 保证某个类, 只能够创建出一个实例, 不能new出多个类.

Java 中的两个经典单例模式 : 

1 . 饿汉模式

2 . 懒汉模式 

    饿汉与懒汉模式介绍 : 

饿汉 : 

举个栗子吧 : 好比平常的脏衣服, 穿完后, 直接洗掉.   (急迫)

懒汉 : 

举个栗子 : 好比平常的脏衣服 , 脏了脱掉之后, 先不洗, 等下次没有干净衣服穿的时候, 我再去洗.  (从容)

上述这两个栗子 : 放在现实生活中 , 肯定是饿汉更好, 脏了就洗, 不然衣服放哪儿都臭了.

而在计算机中 通常认为懒汉模式更好,效率更高  (非必要,不洗)

例如 : 打开一个硬盘上的文件,读取文件的内容,并显示出来.

          饿汉 : 把文件所有内容都读到内存中,并显示

          懒汉 : 只把文件读一小部分 , 把当前屏幕填充满 , 如果要翻页, 再去读其他文件内容, 如若不翻页, 那么内存就剩下来了.

如果文件非常大 10G !!!!!    采用饿汉方式, 文件打开可能要卡半天, 内存够不够还不知道.

                                        如果采用懒汉, 每次我们加载一点,即不影响内存,也不影响我们去读.

二 . 饿汉模式

先看代码实现 : 

public class Singleton {
    // 创建单一模式对象
    private static Singleton instance = new Singleton();
    //将构造方法的权限设置为private 为了确保单一模式实例的唯一性.
    private Singleton() {}
    //提供一个get方法  获取单一变量
    public static Singleton getInstance() {
        return instance;
    }
}

测试类 :  

public class Test {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        System.out.println(instance);
    }
}

饿汉模式 : 在想使用这个对象时, 直接使用这个早都准备好的类成员.

饿汉模式是不是线程安全的?

假设如果多个线程都去调用getInstance()

由于是进行读操作, 因此认为饿汉模式是线程安全的.

三 . 懒汉模式

懒汉模式 : 非必要, 不创建

因此创建对象就是在调用getInstance时进行创建的.

先看这个代码 : 

public class Singleton2 {
    public static Singleton2 instance;
    private Singleton2(){}
    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

分析 : 等其他类去调用getInstance方法时 , 先判断instance是不是空的,如果为空,那么就创建一个对象, 如果不为空那么就返回当前的instance对象.

但 : 思考一个问题 , 如果在多个线程下呢? 

 因此为了确保线程安全 , 就要进行加锁操作.

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

问题又来了 : 

换个思考方向 : 因为instance 只需要被创建一次 , 加锁针对的是在instance == null 的情况下,多个线程同一时间执行 , 以防创建出多个对象 , 等instance创建出来之后 , 锁竞争还是存在,这样就影响了程序的执行效率, 后面的线程想要获得instance 对象 , 都要进行加锁解锁操作, 这是没有必要的, 因为instance 早已经创建出来了. 

因此我们在加锁前面在加上一个 if 判定.

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

到这里还没有结束 : 

指令重排序!!!!

解决 : 加上volatile 关键字

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

单例模式线程安全问题的表现、原因及解决方案如下: ### 问题表现 在多线程环境下,单例模式可能无法保证只创建一个实例。例如,当两个线程同时调用单例对象的创建方法时,由于随机调度和指令重排序的特点,如果一个线程还没有创建出实例,另一个线程就调用,可能会导致多个线程都判断实例为 null,从而创建出多个实例,触发多次 new 操作,不满足单例模式的需求,导致线程不安全 [^2]。对于懒汉模式,多线程调用 getInstance 方法时,有的线程在读,有的线程在写,容易引发线程安全问题,但实例创建好之后,后续的 if 条件不再执行,就不会再引发线程安全问题 [^1]。 ### 产生原因 线程安全问题产生的主要原因是多线程环境下的随机调度和指令重排序。当多个线程同时访问单例对象的创建方法时,由于线程调度的不确定性,可能会出现多个线程同时判断实例为 null 的情况,进而各自创建实例。另外,指令重排序可能会使对象的初始化操作顺序发生改变,导致其他线程看到未完全初始化的对象,从而引发问题 [^2]。 ### 解决方案 - **同步方法**:这种方法简单直接,能有效解决线程安全问题。但是每次调用 getInstance 方法都要进行同步操作,会带来较大的性能开销,特别是在高并发场景下,频繁的加锁和解锁操作会降低系统的整体性能 [^4]。 - **双重检查锁定(DCL)**:使用双重检测锁的懒汉式单例。代码示例如下: ```java private volatile static LazyMan2 lazyMan; // 解决方案 双重检测锁的懒汉式单例 DCL public static LazyMan1 getInstance() { if (lazyMan == null) { synchronized (LazyMan1.class) { if (lazyMan == null) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } lazyMan = new LazyMan1(); // 不是原子性操作 /* * 1. 分配内存空间 * 2. 执行构造方法 初始化对象 * 3. 把这个对象指向这个空间 * volatile 只保证可见性,不保证原子性,但可以防止指令重排 */ } } } return lazyMan; } ``` 在双重检查锁定中,使用 volatile 关键字修饰单例对象,可防止指令重排,同时在同步块内外都进行 null 检查,减少不必要的同步开销 [^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值