线程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);
    }
}

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

相关推荐
最初的↘那颗心40 分钟前
Java 泛型类型擦除
java·flink
IT毕设实战小研1 小时前
基于Spring Boot校园二手交易平台系统设计与实现 二手交易系统 交易平台小程序
java·数据库·vue.js·spring boot·后端·小程序·课程设计
泉城老铁1 小时前
Spring Boot 中根据 Word 模板导出包含表格、图表等复杂格式的文档
java·后端
极客BIM工作室1 小时前
谈谈《More Effective C++》的条款30:代理类
java·开发语言·c++
孤狼程序员1 小时前
【Spring Cloud 微服务】1.Hystrix断路器
java·spring boot·spring·微服务
RainbowSea2 小时前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 04
java·spring boot·后端
用户84913717547162 小时前
JustAuth实战系列(第11期):测试驱动开发 - 质量保证与重构实践
java·设计模式·单元测试
RainbowSea2 小时前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 03
java·spring boot·后端
心月狐的流火号2 小时前
Java SPI 机制与 Spring Boot 自动装配原理
java·spring boot
AAA修煤气灶刘哥2 小时前
面试必问:聊一聊Spring中bean的循环依赖问题 ?——从原理到避坑
java·后端·程序员