线程P5 | 单例模式[线程安全版]~懒汉 + 饿汉

什么是单例模式?

在我们正式讲解单例模式之前,没有了解过的小伙伴可能会有疑问...到底啥叫单例模式??其实单例模式呢,是我们设计模式中的一种,所谓的设计模式,你可以把它理解为一个模板,也就是你在实现某种业务的时候,选择适配的设计模式,根据这个模板来改你对应的业务代码

Java设计模式是解决特定软件设计问题的经典、可复用的方案模板,分为创建型、结构型和行为型三大类,帮助开发者编写更灵活、可维护的代码。

那么我们的单例模式 呢,指的是实例对象只会被创建一次这样的设计模式~~

为了实现这样的要求,单例模式中又有两种形式:饿汉模式 和懒汉模式,接下来我们将会为大家一一介绍这两种模式

饿汉模式

什么是饿汉模式

所谓的饿汉模式,其实指的是实例从代码刚开始运行的时候就已经创建好的模式,那实例就处在一个等着被调用的状态,所以就一直饿着来等待资源~~因此就叫做饿汉模式啦

饿汉模式实现

java 复制代码
class Singleton {   //饿汉模式
    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {

    }

}
public class Demo19 {
    public static void main(String[] args) {
        Singleton t1 = Singleton.getInstance();
        Singleton t2 = Singleton.getInstance();
        boolean res = t1 == t2;
        System.out.println(res);
    }
}

如上,我们运行结果为true,因为此时它们引用的都是同一个实例~~所以是符合单例模式的

我们可以发现,单例模式的实现,即外部无法创建一个实例是通过将构造方法变为private来实现的,此时的话外部只能通过getInstance()方法来访问内部已经创建好的那个实例

线程安全问题

那么,饿汉模式有没有线程安全问题呢?我们可以发现,饿汉模式中只有读操作,所以是没有线程安全问题的~~可以放心大胆使用

不过饿汉模式还是有缺点的,因为实例一开始就被创建了,一直等着被使用,这是很浪费资源的行为~~

懒汉模式

什么是懒汉模式

所谓的懒汉模式,指的是实例只有在被使用的时候才会开始创建,因此听起来就很懒啦,所以就被叫做懒汉模式,不过这里的"懒"可是褒义词,因为这可以节约资源~~

懒汉模式实现

java 复制代码
class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
             if (instance == null) {             //判断是否有实例创建了
                 instance = new SingletonLazy();
             }

        return instance;
    }

    private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式
    public static void main(String[] args) {
        SingletonLazy t1 = SingletonLazy.getInstance();
        SingletonLazy t2 = SingletonLazy.getInstance();
        System.out.println(t1 == t2);
    }
}

上述我们给出了一个按照描述的懒汉模式,实例一开始是null,只有当被调用的时候才会根据判断实例是否创建过来创建实例

线程安全问题

当我们细看上述代码的时候,其实可以发现,对于instance对象,我们既有读操作,又有写操作,所以事实上,这个代码是存在线程安全问题的

当t1和t2同时调用的时候,有可能会创建两个实例,这就不符合单例模式的要求了;

我们细看可以发现,此时创建实例这个操作并不是原子 的,是if + 创建一起的,这也是会引发线程安全问题的原因

因此,为了解决上述问题,我们可以把这个操作加个锁,把它们变成原子的~~

解决原子性问题代码
java 复制代码
class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object locker = new Object();

    public static SingletonLazy getInstance() {
            synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例
                if (instance == null) {             //判断是否有实例创建了
                    instance = new SingletonLazy();
                }
            }

        return instance;
    }

    private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式
    public static void main(String[] args) {
        SingletonLazy t1 = SingletonLazy.getInstance();
        SingletonLazy t2 = SingletonLazy.getInstance();
        System.out.println(t1 == t2);
    }
}

OK~~我们的原子性问题已经解决啦(●'◡'●)

但是...我们再来看下这个代码,线程安全问题有没有解决完呢?回顾我们P4中提到的引起线程安全问题的原因,此时其实我们是两个线程在对一个对象进行操作,所以是很可能发生指令重排序和内存可见性问题的,因此我们应该给这个对象加上volatile~~

解决指令重排序和内存可见性问题
java 复制代码
class SingletonLazy {
    private static volatile SingletonLazy instance = null;  //volatile防止指令重排序
    private static Object locker = new Object();

    public static SingletonLazy getInstance() {
            synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例
                if (instance == null) {             //判断是否有实例创建了
                    instance = new SingletonLazy();
                }
            }

        return instance;
    }

    private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式
    public static void main(String[] args) {
        SingletonLazy t1 = SingletonLazy.getInstance();
        SingletonLazy t2 = SingletonLazy.getInstance();
        System.out.println(t1 == t2);
    }
}

OK~~~这下这两个问题也解决啦(●'◡'●)

这样看起来,线程好像是没啥安全问题了勒,那有没有啥可以优化的?

经典生活例子

为了使大家更好理解我们的优化策略,在po出代码之前,我们先搞个生活化例子理解一下~~

比如说学校里面的校花突然恢复单身了,那广大单身男青年们听到这个消息之后都很开心呐,不过大家素质都很高,于是在追求校花的时候排起了队,按序追求,你很幸运的抢到了第一个,你开始追求校花之后就相当于追求校花这个操作加锁了,别人就不能进行了,校花觉得你特别好,于是答应和你在一起啦~~成功创建了实例,但是队伍里面的人此时还不知道,第二个人此时又去追求校花,追求校花这个操作就又加锁了,这个时候她就说我有男朋友啦,第二个人出去是不是就会告诉其它人,校花有男朋友了这件事情,那么实际上,他们就不会再进行追求校花这个操作了,即甚至连竞争锁这个操作都不会去做,因为这是浪费资源的

所以与例子同样的问题,我们这个代码此时每个线程都还会再去竞争一次锁,但如果实例已经存在了,就没有竞争锁的必要了,只有一开始的几个线程在实例被创建好之前就进入创建实例过程会竞争一下锁~~~

解决重复竞争锁问题
java 复制代码
class SingletonLazy {
    private static volatile SingletonLazy instance = null;  //volatile防止指令重排序
    private static Object locker = new Object();

    public static SingletonLazy getInstance() {
        if (instance == null) {                     //后续线程可以防止重复加锁
            synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例
                if (instance == null) {             //判断是否有实例创建了
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
    }

    private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式
    public static void main(String[] args) {
        SingletonLazy t1 = SingletonLazy.getInstance();
        SingletonLazy t2 = SingletonLazy.getInstance();
        System.out.println(t1 == t2);
    }
}

标准懒汉模式实现代码

我们这里总结一下解决完所有问题之后的代码

java 复制代码
class SingletonLazy {
    private static volatile SingletonLazy instance = null;  //volatile防止指令重排序
    private static Object locker = new Object();

    public static SingletonLazy getInstance() {
        if (instance == null) {                     //后续线程可以防止重复加锁
            synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例
                if (instance == null) {             //判断是否有实例创建了
                    instance = new SingletonLazy();
                }
            }
        }

        return instance;
    }

    private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式
    public static void main(String[] args) {
        SingletonLazy t1 = SingletonLazy.getInstance();
        SingletonLazy t2 = SingletonLazy.getInstance();
        System.out.println(t1 == t2);
    }
}

❤❤感谢观看~~对你有帮助的话给博主点个大大的赞吧(●'◡'●)谢谢~~~❤❤

相关推荐
SL_staff35 分钟前
制造业交付崩盘?用跨部门节点协同计划打通销售、生产与质量链路
java·开源·产品
文丑颜不良啊1 小时前
LeetCode2261~2280题目解析
java·leetcode
SL_staff2 小时前
告别规则黑盒:JVS-Rules 在金融风控中的可视化决策编排实践
java·产品·全栈
逃逸线LOF2 小时前
服务网关Gateway
java·运维·gateway
灿飞屋3 小时前
Kotlin与Java虚拟机四:函数
java·python·kotlin·安卓·android jetpack
阡陌数智4 小时前
Claude API新版本重大变更与安全升级指南
java·linux·安全
学长毕设5 小时前
基于SpringBoot的野生动物公益保护平台设计与实现-附源码
java·spring boot·后端
SL_staff6 小时前
离散制造排程破局:JVS-APS 四步闭环落地实践(开发者视角)
java·产品·全栈
这个昵称不容易6 小时前
SpringBoot 美容院管理系统:232个Java文件315个接口,5套角色从预约到发货一整条链路
java·springboot·毕设
装不满的克莱因瓶7 小时前
【SpringAI Alibaba】ReactAgent记忆管理MessageTrimmingHook与MessageDeletionHook
java·人工智能·ai·ai编程·springai·springaialibaba