多线程---单例模式

文章目录

什么是单例模式?

单例模式:是一种常见的设计模式。即:一些大佬针对一些常见的需求场景,整理出来的一些解决方案。我们只需要套用模式就可以解决问题,极大的便利了我们的开发。

具体来说,单例模式就要求某个类只能有一个实例。

那在Java中怎么实现呢?

我们就会想到"static"这个关键字,static修饰的成员/属性就会变成类成员/属性。当属性变成类属性的时候其实就已经是"单例"的了。因为,JVM在加载类的时候只加载一次,这个类是"单例"的,那么它包含的属性更是"单例"的。

借助static实现"单例模式"有两种模式:饿汉模式和懒汉模式

饿汉模式

java 复制代码
//饿汉模式   在类加载阶段就创建了实例   实例创建的时机非常早  非常急迫
class Singleton{
    // static 修饰的变量是类对象的成员变量   JVM加载时只加载一次
    private static Singleton instance = new Singleton();

    public static Singleton getSingleton() {
        return instance;
    }

    // 设置私有的构造方法  防止有人不通过getSingleton方法来获取到我们规定的单个示例   而是在别的类中new Singleton来创建实例
    private Singleton(){

    }
}

注:

  1. Singleton类的构造方法应该设置为私有的,防止别人不使用我们已经创建好的实例再创建新的实例。
  2. instance应该被private static修饰,构造方法被设置为私有的后,只有通过一个公共的static方法才能获取到实例
  3. 在类加载的时候就创建了实例,创建实例的时机非常紧迫,所以叫"饿汉模式"

懒汉模式

版本一:最简单的懒汉模式

java 复制代码
//懒汉模式:在需要使用到实例的时候再创建实例,创建实例的时机不紧迫
class SingletonLazy{
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        if (instance == null){
            instance = new SingletonLazy();

        }
        return instance;
    }

    private SingletonLazy(){

    }
}

注:

  1. 在首次调用到getInstance的时候才会创建实例,后续再调用到getInstance的时候直接返回已经创建好的实例。
  2. 饿汉模式的效率更高。原因一:如果后续没人需要使用到实例,则创建实例的过程就被节省下来了;原因二:在类加载的过程中,JVM需要做的工作非常多。把创建实例的过程延后,可以给JVM减轻点负担。

版本二:考虑懒汉模式存在的线程安全问题

在饿汉模式中,调用到getInstance方法只涉及到读操作,不会有线程安全问题。

在懒汉模式中,调用到getInstance方法会进行判断后再创建实例,既涉及到读又涉及到修改。在多线程环境下就有可能创建多个实例,违背了单例模式

java 复制代码
// 线程安全的懒汉模式
class SingletonLazy{
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        //加锁 保证线程安全 只能创建单个实例
        synchronized (SingletonLazy.class) {
            if (instance == null) {
                instance = new SingletonLazy();

            }
        }
        return instance;
    }

    private SingletonLazy(){

    }
}

版本三:更完善的解决线程安全问题

像上面那样直接加锁,我们虽然可以保证线程是安全的。但是有一个新的问题 :懒汉模式只有在第一次创建实例的时候才有线程安全问题,后续使用实例的时候直接返回实例就行,不需要加锁判断。但是像上面那样,在线程安全时也会加锁,降低了代码执行效率

java 复制代码
// 懒汉模式更加完善的解决线程安全问题
class SingletonLazy{
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){
        //判断是否需要加锁
        if (instance == null) {

            synchronized (SingletonLazy.class) {
                // 判断是否已经创建实例

                //这个if不可少  当线程1 2 同时调用getInstance方法时  线程1执行完会创建实例
                // 如果没有if  线程2来了不知道是否已经有实例了 还会继续创建实例
                if (instance == null) {
                    instance = new SingletonLazy();

                }
            }
        }
        return instance;
    }

    private SingletonLazy(){

    }
}

注:两个if都必不可少,各自有各自的作用:第一个if:在已经创建实例后,判断释放需要继续加锁;第二个if:在没有创建实例时,防止创建多个实例。

版本四:解决指令重排序问题

一个新的问题

new操作分为三步(粗略): 1.申请内存 2. 初始化实例 3. 将内存首地址赋值给instance

编译器可能进行指令重排序的优化 ,比如:

线程1 将1-2-3 的执行顺序 改为 1-3-2 并且在执行完1-3后 线程2调用getInstance 。线程2看到instance里已经有了地址就会直接返回 , 但是未初始化。在后续调用实例的属性/方法时会有问题!

java 复制代码
class SingletonLazy{
    //加上volatile 禁止指令重排序
    private volatile static SingletonLazy instance = null;

    public static SingletonLazy getInstance(){

        if (instance == null) {

            synchronized (SingletonLazy.class) {

                if (instance == null) {
                    instance = new SingletonLazy();

                }
            }
        }
        return instance;
    }

    private SingletonLazy(){

    }
}
相关推荐
面朝大海,春不暖,花不开8 分钟前
自定义Spring Boot Starter的全面指南
java·spring boot·后端
得过且过的勇者y8 分钟前
Java安全点safepoint
java
夜晚回家43 分钟前
「Java基本语法」代码格式与注释规范
java·开发语言
斯普信云原生组1 小时前
Docker构建自定义的镜像
java·spring cloud·docker
wangjinjin1801 小时前
使用 IntelliJ IDEA 安装通义灵码(TONGYI Lingma)插件,进行后端 Java Spring Boot 项目的用户用例生成及常见问题处理
java·spring boot·intellij-idea
wtg44521 小时前
使用 Rest-Assured 和 TestNG 进行购物车功能的 API 自动化测试
java
白宇横流学长1 小时前
基于SpringBoot实现的大创管理系统设计与实现【源码+文档】
java·spring boot·后端
fat house cat_2 小时前
【redis】线程IO模型
java·redis
stein_java3 小时前
springMVC-10验证及国际化
java·spring
weixin_478689763 小时前
C++ 对 C 的兼容性
java·c语言·c++