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

相关推荐
非优秀程序员9 小时前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻10 小时前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
沐晓时光10 小时前
C语言入门,内存函数知识讲解
c语言·开发语言
文丑颜不良啊12 小时前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang13 小时前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪13 小时前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息13 小时前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼14 小时前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff15 小时前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈
SL_staff15 小时前
企业AI落地难?JVS-AI渐进式实践:安全可控、小切口验证、标准化沉淀与规模化复制
java·开源·aigc