结论先行

  1. 饿汉式:线程安全但可能造成资源浪费,推荐在初始化成本低的场景使用
  2. 懒汉式:需要解决线程安全问题,推荐使用双重检查锁+volatile优化
  3. 静态内部类:最佳实践方案,完美平衡延迟加载与线程安全
  4. 枚举单例:JDK1.5+推荐方案,天然防反射/序列化破坏
  5. 实际开发中优先选择枚举或静态内部类实现

文章持续更新,可以微信搜一搜「 半个脑袋儿 」第一时间阅读


一、核心实现方式

1. 饿汉式
class ClassA {
private static final ClassA INSTANCE = new ClassA(); public static ClassA getInstance() {
return INSTANCE;
} private ClassA() {} // 防止反射创建
}

特点

  • 类加载时立即初始化(可能造成资源浪费)
  • 天然线程安全
  • 需处理反射攻击(添加私有构造器判空逻辑)
2. 双重检查锁懒汉式
class ClassB {
private static volatile ClassB instance; public static ClassB getInstance() {
if (instance == null) { // 第一次检查
synchronized (ClassB.class) { // 同步锁
if (instance == null) { // 第二次检查
instance = new ClassB();
}
}
}
return instance;
} private ClassB() {}
}

关键点

  • volatile防止指令重排序(JDK5+的JMM修复)
  • 两次null检查确保性能与线程安全
  • 仍可能被反射破坏单例
3. 静态内部类
class ClassC {
private static class Holder {
static final ClassC INSTANCE = new ClassC();
} public static ClassC getInstance() {
return Holder.INSTANCE;
} private ClassC() {}
}

优势

  • 利用类加载机制保证线程安全
  • 实现延迟加载(调用getInstance时才会初始化)
  • 代码简洁无锁
4. 枚举式
enum EnumSingleton {
INSTANCE; public void businessMethod() {
// 业务方法
}
}

绝对优势

  • 天生防反射攻击(枚举类没有构造器)
  • 自动处理序列化/反序列化
  • 代码极度简洁

二、实战应用场景

1. 配置管理类
public enum ConfigManager {
INSTANCE; private Properties props = new Properties(); ConfigManager() {
try(InputStream is = getClass().getResourceAsStream("/app.properties")) {
props.load(is);
}
} public String getProperty(String key) {
return props.getProperty(key);
}
}
2. 数据库连接池
public class ConnectionPool {
private static final int MAX_SIZE = 100;
private BlockingQueue<Connection> pool = new ArrayBlockingQueue<>(MAX_SIZE); private static class Holder {
static final ConnectionPool INSTANCE = new ConnectionPool();
} private ConnectionPool() {
// 初始化连接池
} public static ConnectionPool getInstance() {
return Holder.INSTANCE;
} public Connection getConnection() throws InterruptedException {
return pool.take();
}
}
3. Spring中的单例
  • Spring默认的Bean作用域就是单例
  • 通过IOC容器管理生命周期
  • 与设计模式单例的区别:每个容器对应一个实例

三、高频面试题解析

Q1:DCL(双重检查锁)为什么要加volatile?

:防止指令重排序导致返回未初始化完成的对象。new操作不是原子操作,分为:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存地址

不加volatile可能导致步骤2和3重排序,其他线程可能拿到未初始化完成的对象。

Q2:如何防止反射攻击?
private ClassC() {
if (Holder.INSTANCE != null) {
throw new RuntimeException("禁止反射创建!");
}
}
Q3:枚举单例如何防止反射?
  • 枚举类的构造方法由JVM特殊处理
  • 反射newInstance方法会直接抛出异常
Q4:单例对象什么时候会被回收?
  • 只有当加载该类的ClassLoader被回收时才会被回收
  • 一般情况(使用系统类加载器)会与JVM生命周期一致
Q5:单例模式的优缺点?

优点

  • 内存中只有一个实例,减少内存开销
  • 避免对资源的多重占用

缺点

  • 违背单一职责原则(既要管理实例又要处理业务)
  • 扩展困难(需要修改源码)
  • 测试困难(全局状态难以隔离)

Java单例模式:从实战到面试的深度解析的更多相关文章

  1. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

  2. 【转】java内存分配和String类型的深度解析

    一.引题 在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题.下面是本 ...

  3. Java字符串池(String Pool)深度解析

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 在工作中,String类是我们使用频率非常高的一种对象类型.JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存 ...

  4. Java 的抽象特性:抽象类与接口深度解析

    要点: 抽象类 接口 抽象类与接口的差别 一. 抽象 对于面向对象编程来说,抽象是它的四大特征之中的一个. 在Java中,能够通过两种形式来体现OOP的抽象:接口和抽象类. 接口和抽象类为我们提供了一 ...

  5. Java字符串池(String Pool)深度解析(转)

    出自  http://www.cnblogs.com/fangfuhai/p/5500065.html 在工作中,String类是我们使用频率非常高的一种对象类型.JVM为了提升性能和减少内存开销,避 ...

  6. 重学 Java 设计模式:实战单例模式

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 5个创建型模式的最后一个 在设计模式中按照不同的处理方式共包含三大类:创建型模式.结 ...

  7. 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」

    作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...

  8. 【深入】java 单例模式(转)

    [深入]java 单例模式 关于单例模式的文章,其实网上早就已经泛滥了.但一个小小的单例,里面却是有着许多的变化.网上的文章大多也是提到了其中的一个或几个点,很少有比较全面且脉络清晰的文章,于是,我便 ...

  9. Java软件系统功能设计实战训练视频教程

    Java软件系统功能设计实战训练视频教程 第01节课:整体课程介绍和杂项介绍第02节课:软件功能设计常见理念和方法第03节课:关于软件设计的一些思考第04节课:第一周作业的业务和相应模式:综合应用简单 ...

  10. jvm--深入理解java虚拟机 精华总结(面试)(转)

    深入理解java虚拟机 精华总结(面试)(转) 原文地址:http://www.cnblogs.com/prayers/p/5515245.html 一.运行时数据区域 3 1.1 程序计数器 3 1 ...

随机推荐

  1. [记录点滴] 小心 Hadoop Speculative 调度策略

    [记录点滴] 小心 Hadoop Speculative 调度策略 目录 [记录点滴] 小心 Hadoop Speculative 调度策略 [0x00] 摘要 [0x01] 缘由 [0x02] 代码 ...

  2. 流程控制之do while循环

    语法 do {    //代码语句}while(布尔表达式): 与while的区别 while是先判断再执行,do while是先执行再判断 循环体至少会被执行一次 实例1: package com. ...

  3. .Net Core 项目启动方式

    本文篇幅较小,讲解如何通过命令行启动项目 接着上一章的Core WebApi(https://www.cnblogs.com/zousc/p/12420998.html),我们已经有了Hello这个控 ...

  4. Flink学习(十八) 状态管理与状态编程

    Flink中的状态 由一个任务维护,并且用来计算某个结果的所有数据,都属于这个任务的状态:可以认为状态就是一个本地变量,可以被任务的业务逻辑访问:Flink会进行状态管理,包括状态一致性,故障处理以及 ...

  5. CMD批处理脚本+VBScript脚本+Potplayer 实现文件夹内所有视频的截图任务(指定时间点)

    实现自动化视频截图,一般会直接借视频编解码如FFmpeg,动用相关函数来实现,直接从解码源头设计程序.然而我没有接触过FFmpeg,借助cmd批处理,以及vbs,还有现成的播放器potplayer,一 ...

  6. C#之 Dictionary 详解

    基本概念 Dictionary<TKey, TValue>是C#中用于存储键值对集合的泛型类,属于System.Collections.Generic命名空间.它允许使用键(Key)来访问 ...

  7. Selenium WebDriver上创建 WebDriver测试脚本

    本文实现一个WebDriver测试脚本,介绍WebDrive的常用命令.UI元素定位的策略以及在脚本中的使用,还有Get命令. 你将学到:  脚本创建  代码走查  测试执行  定位Web元素 ...

  8. Manjora配置记录

    22/9/12 目前的启动项有3:Windows Boot Manager.Manjaro.UEFI OS.其中UEFI OS 和 Manjaro 进入后内容相同:Windows下检测不到Manjar ...

  9. 【译】Visual Studio 中新的强大生产力特性

    有时候,生活中的小事才是最重要的.在最新版本的 Visual Studio 中,我们增加了一些功能和调整,目的是让您脸上带着微笑,让您更有效率.这里是其中的一些列表,如果您想要完整的列表,请查看发行说 ...

  10. MySQL 是否可以用 Docker 容器化?

    容器 容器是为了解决 "在切换运行环境时,如何保证软件能够正常运行",容器是轻量级应用代码包,它包含在任何环境中运行所需的所有元素的软件包.容器可以虚拟化操作系统,包含依赖项,例如 ...