Java 性能调优实战
刘超
前金山软件技术经理
59174 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
开篇词 (1讲)
模块一 · 概述 (2讲)
结束语 (1讲)
Java 性能调优实战
15
15
1.0x
00:00/00:00
登录|注册

27 | 单例模式:如何创建单一对象优化系统性能?

使用volatile关键字
减少同步锁资源竞争
内部类实现
双重检查
加同步锁
懒加载
占用堆内存
高性能
多线程情况下实例唯一性
类初始化时即创建实例
其他实现方式
选择方式根据需求
优化
实现方式
缺点
优点
实现方式
提供一个全局访问点
保证一个类仅创建一个实例
思考题
总结
懒汉模式
饿汉模式
什么是单例模式?
单例模式

该思维导图由 AI 生成,仅供参考

你好,我是刘超。
从这一讲开始,我们将一起探讨设计模式的性能调优。在《Design Patterns: Elements of Reusable Object-Oriented Software》一书中,有 23 种设计模式的描述,其中,单例设计模式是最常用的设计模式之一。无论是在开源框架,还是在我们的日常开发中,单例模式几乎无处不在。

什么是单例模式?

它的核心在于,单例模式可以保证一个类仅创建一个实例,并提供一个访问它的全局访问点。
该模式有三个基本要点:一是这个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
结合这三点,我们来实现一个简单的单例:
//饿汉模式
public final class Singleton {
private static Singleton instance=new Singleton();//自行创建实例
private Singleton(){}//构造函数
public static Singleton getInstance(){//通过该函数向整个系统提供实例
return instance;
}
}
由于在一个系统中,一个类经常会被使用在不同的地方,通过单例模式,我们可以避免多次创建多个实例,从而节约系统资源。

饿汉模式

我们可以发现,以上第一种实现单例的代码中,使用了 static 修饰了成员变量 instance,所以该变量会在类初始化的过程中被收集进类构造器即 <clinit> 方法中。在多线程场景下,JVM 会保证只有一个线程能执行该类的 <clinit> 方法,其它线程将会被阻塞等待。
等到唯一的一次 <clinit> 方法执行完成,其它线程将不会再执行 <clinit> 方法,转而执行自己的代码。也就是说,static 修饰了成员变量 instance,在多线程的情况下能保证只实例化一次。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

单例模式是一种常用的设计模式,用于确保一个类仅创建一个实例,并提供一个全局访问点。本文介绍了单例模式的基本概念和懒汉模式的实现方式。懒汉模式通过懒加载方式,在系统使用到类对象时才将实例加载到堆内存中。然而,在多线程情况下可能会出现实例化多个类对象的问题。文章详细介绍了使用Synchronized同步锁和Double-Check的方式来解决多线程下的单例实例化问题,并讨论了Happens-Before规则和重排序的影响。此外,文章还介绍了通过内部类实现的懒汉模式,避免了多线程下重复创建对象的情况。总结了单例模式的两种实现方式:饿汉模式和懒汉模式,并提出了根据需求选择合适的实现方式的建议。整体而言,本文深入浅出地介绍了单例模式的实现方式,为读者提供了全面的技术知识和实践经验。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 性能调优实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(41)

  • 最新
  • 精选
  • Loubobooo
    使用枚举来实现单例模式,具体代码如下:public class SinletonExample5 { private static SinletonExample5 instance = null; // 私有构造函数 private SinletonExample5(){ } public static SinletonExample5 getInstance(){ return Sinleton.SINLETON.getInstance(); } private enum Sinleton{ SINLETON; private SinletonExample5 singleton; // JVM保证这个方法只调用一次 Sinleton(){ singleton = new SinletonExample5(); } public SinletonExample5 getInstance(){ return singleton; } } }

    作者回复: 很赞!这是一种懒加载模式的枚举实现。

    2019-07-23
    7
    55
  • 豆泥丸
    最安全的枚举模式,反射和序列化都是单例。

    作者回复: 对的,我们之前序列化优化这一讲中的问答题就是与枚举实现单例相关,《Effective Java》作者也是强烈推荐枚举方式实现单例。

    2019-07-23
    4
    30
  • Jxin
    1.可能大部分同学都知道,但为了少部分同学,我在老师这个单例上补个点。其它线程空指针异常确实是指令重排导致的,但其原因还有一个。加锁并不能阻止cpu调度线程执行体,所以时间片还是会切的(假设单核),所以其他线程依旧会执行锁外层的if(),并发情况下就可能拿到仅赋值引用,未在内存空间存储数据的实例(null实例),进而空指针。 2.给老师的代码补段骚的: // 懒汉模式 + synchronized 同步锁 + double-check public final class Singleton { private static validate Singleton instance = null;// 不实例化 public List<String> list;//list 属性 private Singleton(){ list = new ArrayList<String>(); }// 构造函数 public static Singleton getInstance(){// 加同步锁,通过该函数向整个系统提供实例 Singleton temp = instance; if(null == temp){// 第一次判断,当 instance 为 null 时,则实例化对象,否则直接返回对象 synchronized (Singleton.class){// 同步锁 temp = instance; if(null == temp){// 第二次判断 temp = new Singleton();// 实例化对象 instance = temp; } } } return instance;// 返回已存在的对象 } } 用临时变量做方法内数据承载(相对于validate修饰的属性,可以减少从内存直接拷贝数据的次数),最后用instance接收临时变量时,因为是validate修饰,所以也不会有指令重排。所以前面临时变量的赋值操作已经完成,这样instance就必然是赋值好的实例。(如有错误请老师指出,仅个人理解的骚操作) 3.极限编程试试就好,业务代码还是尽量优先保证可读性,只有在有性能需求时再采用影响可读性的性能优化。我的这种骚写法和老师的内部类,这种看起来需要想那么一下的东西尽量避免,简单才是王道。

    作者回复: 虽然有点绕,还是值得表扬的。我们还是鼓励简单易懂的编程风格。

    2019-07-23
    9
    12
  • 行者
    枚举也是一种单例模式,同时是饿汉式。 相比Double Check,以内部类方式实现单例模式,代码简洁,性能相近,在我看来是更优的选择。

    作者回复: 也可以使用枚举实现懒汉模式,可以根据本讲中的使用内部类方式实现懒加载。

    2019-07-23
    10
  • -W.LI-
    枚举底层实现就是静态内部类吧

    作者回复: 对的,枚举是一种语法糖,在Java编译后,枚举类中的枚举会被声明为static,接下来就跟我们文中讲的一样了。

    2019-07-23
    8
  • 我又不乱来
    枚举天生就是单例,但是不清楚这么实现。 注册式单例,spring应该是用的这种。这个也不太清楚,超哥有机会讲一下spring的实现方式和枚举方式实现的单例。谢谢😁

    作者回复: Spring中的bean的单例虽然是一种单例效果,但实现方式是通过容器缓存实现,严格来说是一种享元模式。

    2019-07-23
    6
  • 张三丰
    如果把这个成员变量的static去掉,在多线程情况下就可能创建多个实例,单线程没问题。老师,这么理解没问题吧? // 饿汉模式 public final class Singleton { private Singleton instance=new Singleton();// 自行创建实例 private Singleton(){}// 构造函数 public static Singleton getInstance(){// 通过该函数向整个系统提供实例 return instance; } }

    作者回复: 这个没法调用的哦,静态方法无法调用非静态成员变量。

    2019-10-22
    5
  • Zed
    容器类管理 class InstanceManager { private static Map<String, Object> objectMap = new HashMap<>(); private InstanceManager(){} public static void registerService(String key,Object instance){ if (!objectMap.containsKey(key)){ objectMap.put(key,instance); } } public static Object getService(String key){ return objectMap.get(key); } }

    作者回复: Spring中bean的单例就是使用容器来实现的,便于管理。

    2019-07-23
    3
    4
  • 我知道了嗯
    枚举实现单例

    作者回复: 对的

    2019-07-23
    3
  • 风轻扬
    老师。枚举单例可以防止反射攻击、序列化攻击。但是,我们要获取的实例化对象怎么防止暴力反射呢?我现在的做法是在实例化对象的私有构造器中加判断,如果暴力反射,直接抛出运行异常。老师有没有好的办法?百思不得其解

    作者回复: 可以通过反序列化对象白名单来控制运行反序列哪些对象,这种方式需要重写resolveClass 方法,具体参考09讲: 极客时间版权所有: https://time.geekbang.org/column/article/99774

    2019-09-11
    1
收起评论
显示
设置
留言
41
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部