尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

单例模式:线程安全,以及volatile关键字

单例模式:线程安全,以及volatile关键字
📅 发布时间:2026/6/21 22:53:34

对于OOP语言而言,每new() 一个对象,就会有一个对象实例生成。但是很多时候需要在程序运行时全局使用同一个实例,避免生成多余的实例或者资源浪费。这类设计模式就是单例模式。

单例模式有如下要求:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

其中第二和第三点都是为了给第三点打补丁,如果不是由单例类本身创建和提供实例,那么就没办法在全局中确保使用同一个实例。单例模式通过设置私有的构造函数以防止外部创建对象,为此,需要设置一个对外接口getInstance()方法提供对象。

饿汉式

public class SingleObject {  // 1.饿汉模式,类加载时通过静态关键字就创建好对象private static SingleObject instance = new SingleObject();  private SingleObject() {  }  public static SingleObject getInstance() {  return instance;  }  }

饿汉式单例模式认为对象总会使用,因此提前创建好对象。这种方式线程安全,但是在类加载时就初始化,浪费内存,容易产生垃圾对象。

测试可以发现得到的是同一对象:

public class Main {  public static void main(String[] args) {  for (int i = 0; i < 50; i++) {  SingleObject instance = SingleObject.getInstance();  System.out.println(instance);  }  }  
}

输出:

SingleObject.SingleObject@1b6d3586
SingleObject.SingleObject@1b6d3586
SingleObject.SingleObject@1b6d3586
SingleObject.SingleObject@1b6d3586
SingleObject.SingleObject@1b6d3586
……

懒汉式

public class SingleObject {  //2. 懒汉模式  private static SingleObject instance;  private SingleObject() {  }  public static SingleObject getInstance() {  if (instance == null) {  instance = new SingleObject();  }  return instance;  }  }

和饿汉式单例模式不同,懒汉式单例模式只在外部第一次需要对象的时候才创建。这种方式也被称为懒加载(Lazyload),spring中的@Lazy注解通过类似的方式实现类的懒加载。避免了饿汉式浪费内存的问题。但是在多线程并发的情况下,由于多个线程可能同时进入判断if (instance == null),可能会导致多个实例的创建。

并发情况下测试代码:

public class Main {  public static void main(String[] args) throws InterruptedException {  int threadCount = 10;  CountDownLatch countDownLatch = new CountDownLatch(threadCount);  Runnable runnable = new Runnable() {  @Override  public void run() {  SingleObject instance = SingleObject.getInstance();  System.out.println(instance + ",线程" +  Thread.currentThread().getName());  countDownLatch.countDown();  }  };  for (int i = 0; i < threadCount; i++) {  new Thread(runnable).start();  }  }  
}

运行结果:

SingleObject.SingleObject@65a9a79e,线程Thread-4
SingleObject.SingleObject@65a9a79e,线程Thread-5
SingleObject.SingleObject@65a9a79e,线程Thread-2
SingleObject.SingleObject@6517209b,线程Thread-9 //新实例
SingleObject.SingleObject@65a9a79e,线程Thread-7
SingleObject.SingleObject@65a9a79e,线程Thread-3
SingleObject.SingleObject@65a9a79e,线程Thread-8
SingleObject.SingleObject@65a9a79e,线程Thread-1
SingleObject.SingleObject@2f992f4f,线程Thread-0 //新实例
SingleObject.SingleObject@65a9a79e,线程Thread-6
Process finished with exit code 0

懒汉式+双重检查

线程安全的加锁方式

避免线程不安全的方式最常用的就是加锁,对于懒汉式的改进很简单,就是直接加锁。

public class SingleObject {  //2. 懒汉模式  private static SingleObject instance;  private SingleObject() {  }  public static SingleObject getInstance() {  synchronized (SingleObject.class){  if (instance == null) {  instance = new SingleObject();  }  }  return instance;  }  
}

双重检查

加锁很重要的一点就是性能差,会影响效率。为此给出双重检查版本。

public class SingleObject {  //2. 懒汉模式  private static SingleObject instance;  private SingleObject() {  }  public static SingleObject getInstance() {  if (instance == null){  // 第一次检查,如果不为null,直接返回synchronized (SingleObject.class){  if (instance == null) {  // 第二次检查instance = new SingleObject();  }  }  }  return instance;  }  
}

结果

SingleObject.SingleObject@7a5f6ff8,线程Thread-5
SingleObject.SingleObject@7a5f6ff8,线程Thread-9
SingleObject.SingleObject@7a5f6ff8,线程Thread-7
SingleObject.SingleObject@7a5f6ff8,线程Thread-2
SingleObject.SingleObject@7a5f6ff8,线程Thread-3
SingleObject.SingleObject@7a5f6ff8,线程Thread-4
SingleObject.SingleObject@7a5f6ff8,线程Thread-8
SingleObject.SingleObject@7a5f6ff8,线程Thread-0
SingleObject.SingleObject@7a5f6ff8,线程Thread-1
SingleObject.SingleObject@7a5f6ff8,线程Thread-6Process finished with exit code 0

volatile关键字

private volatile static SingleObject instance;  

然而以上的双重检查并不能完全保障完美实现单例模式,因为instance = new Singleton()并非原子操作,包含给对象实例分配内存空间,调用构造方法初始化对象,将对象指向分配的内存空间三步。这三步可能由于提高执行效率而改变执行顺序。

a. memory = allocate() //分配内存  写操作
b. ctorInstanc(memory) //初始化对象 写操作
c. instance = memory   //设置instance指向刚分配的地址 写操作

被volatile关键字修饰的变量被称为volatile变量。通过插入内存屏障禁止指令重排序,保证写操作前的代码不会重排到写操作之后,读操作后的代码不会重排到读操作之前。此时会按照a-b-c的顺序执行。

如果没有加上volatile关键字,上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。在多线程的情况下会出现以下问题。当线程A在执行instance = new Singleton()时,B线程进来执行到第一次if (instance == null)。假设此时A执行的过程中发生了指令重排序,即先执行了a和c,没有执行b。那么由于A线程执行了c导致instance指向了一段地址,所以B线程判断instance不为null,会直接返回一个未初始化的对象。

参考

Java设计模式—单例模式的实现方式和使用场景-阿里云开发者社区
Java中Volatile关键字详解 - 郑斌blog - 博客园
Java volatile关键字最全总结:原理剖析与实例讲解(简单易懂)-CSDN博客

相关新闻

  • 用 Python 和 Tesseract 实现验证码识别
  • 基于 Weiler–Atherton 算法的 IoU 求解
  • 25.9.13 字符编码标准

最新新闻

  • N4_04_词汇_释义
  • 从零构建PMSM伺服驱动器:FOC算法、硬件设计与DSP实现全解析
  • 基于CBF与CCG的未知动态障碍物概率安全导航方法详解
  • 正规API中转站怎么判断?个人和企业如何挑选稳定又合规的AI API接口(附Dify、Cursor配置、报错排查、密钥安全全攻略)
  • 如何让老旧电视重获新生?MyTV-Android开源电视直播应用完全指南
  • 终极指南:如何使用Harepacker-resurrected打造属于你的冒险岛世界 [特殊字符]

日新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号