Java多线程中的单例模式

单例模式

饿汉单例模式

单例模式的基本含义:一个对象在JVM中始终只存在一个。要做到看起来比较简单:只需要给类创建一个实例即可。但实际可不是看起来这么简单。

单例模式设计:

  1. 创建一个静态变量,用于存放当前的对象地址。
  2. 提供该类实例的get方法。得是静态的
  3. 将构造方法私有化。
    设计如下:
java 复制代码
class SingleTon{
    private static SingleTon instance = new SingleTon();
    public static SingleTon getInstance(){
        return instance;
    }
    private SingleTon(){}
}

使用:

java 复制代码
public class HungrySingleton {
    public static void main(String[] args) {
        SingleTon s1 = SingleTon.getInstance();
        SingleTon s2 = SingleTon.getInstance();
        System.out.println(s1==s2); // true
    }
}

这里的s1和s2指的是同一个对象。

但是

这个对象创建的太早了,在类加载的时候就实例化了,故这种模式也称之为饿汉模式(一个人太饿了就饥不择食就吃的很早)。

懒汉单例模式

可以晚点创建对象,在用户调用getInstance方法时再创建对象。

设计如下:

java 复制代码
class LazySingle{
    private static LazySingle instance = null;
    public  static LazySingle getInstance(){
        if(instance == null) {
                if (instance == null) {
                    instance = new LazySingle();
                }
        }
        return instance;
    }
    private LazySingle(){};
}

如此,在调用getInstance方法时再创建对象,称之为懒汉单例模式。

但是这个设计会有线程安全的问题。

当多个线程都调用getInstance方法时,若都进入了if语句,那么创建的对象就不止一个了,极大浪费系统资源。

线程安全的懒汉单例模式

那么就可以使用synchronized对代码进行同步,保证线程安全。

如下:

java 复制代码
class LazySingle{
    private static LazySingle instance = null;
    public  static LazySingle getInstance(){
     // 通过synchronized加锁。
            synchronized (LazySingle.class) {
                if (instance == null) {
                    instance = new LazySingle();
                }
        }
        return instance;
    }
    private LazySingle(){};
}

加锁之后就可以保证线程安全了。但是又出现了一个问题:每次调用getIntance方法之后,都会加锁。(加锁是一个资料消耗极大的过程,故会降低效率)所以,需要在外层添加一个判断,当instance变量不为空的之后,才进行加锁并进行new操作。

故需要改进:

java 复制代码
class LazySingle2{
    private static LazySingle2 instance = null;
    public  static LazySingle2 getInstance(){
        // 外层if,主要是为了提高效率,判定是否需要加锁。
            if(instance == null) {
                synchronized (LazySingle2.class) {
                    // 这里的 if判断是否需要new对象。
                    if (instance == null) {
                        instance = new LazySingle2();
                    }
                }
        }
        return instance;
    }
    private LazySingle2(){};
}

现在基本实现了一个线程安全的单例模式了。但是还有优化的空间。

指令重排序:编译器为了提高效率,在保证逻辑不变的前提下,会优化指令的执行顺序。但是在多线程的代码中,这个前提很难保证。

当线程t1准备通过new创建该对象时。可能会将new的底层操作进行指令重排序。

例如:new的过程主要是三步:

  1. 申请堆空间
  2. 在堆空间上构造对象
  3. 将堆空间的地址返回给istance对象

但是编译器为了提高效率,会变成132的执行顺序,所以instance会指向一个不合法的空间(这个空间上的还没来得及构造对象),那么此时instance引用就不为空了。故当其他的线程t2执行getInstance方法时,外层if判断不成立,会直接返回instance引用。故t2线程得到了一个指向不合法空间的instance,导致误操作。

解决办法:给instance变量加上volatile关键字。volatile关键字会阻止编译器进行指令重排序,保证逻辑正确。

改进如下:

java 复制代码
class LazySingle3{
    private volatile static LazySingle3 instance = null;
    public  static LazySingle3 getInstance(){
        // 外层if,主要是为了提高效率,判定是否需要加锁。
        if(instance == null) {
            synchronized (LazySingle3.class) {
                // 这里的 if判断是否需要new对象。
                if (instance == null) {
                    instance = new LazySingle3();
                }
            }
        }
        return instance;
    }
    private LazySingle3(){};
}

至此:这就是终极版的java多线程的单例模式的实现。当然后续如果通过反射创建对象也可能会存在多线程的安全问题,但是这种情况太极端了,实际项目开发中几乎不会出现。故忽略该场景。

相关推荐
小途软件1 天前
基于深度学习的人脸属性增强器
java·人工智能·pytorch·python·深度学习·语言模型
linzihahaha1 天前
C++ 单例模式总结
开发语言·c++·单例模式
Lancer-311 天前
打开JAVA控制台(Java control panel )
java·开发语言
Hcoco_me1 天前
大模型面试题46:在训练7B LLM时,如果使用AdamW优化器,那么它需要的峰值显存是多少?
开发语言·人工智能·深度学习·transformer·word2vec
技术小泽1 天前
MQTT从入门到实战
java·后端·kafka·消息队列·嵌入式
福大大架构师每日一题1 天前
milvus v2.6.8 发布:搜索高亮上线,性能与稳定性全面跃升,生产环境强烈推荐升级
android·java·milvus
键盘林1 天前
java: 找不到符号
java
半夏知半秋1 天前
rust学习-Option与Result
开发语言·笔记·后端·学习·rust
、BeYourself1 天前
项目案例-构建 AI 驱动的文档搜索系统-2
java·人工智能·springai·项目案例