Java中单例模式的几种实现方式总结
单例模式(Singleton Pattern)是Java中最基本和最常用的设计模式之一。该模式确保一个类只有一个实例,并提供一个全局访问点来访问该实例。在需要频繁创建和销G毁对象或者需要一个全局唯一的对象(例如,线程池、缓存、日志对象等)的场景下,单例模式尤为重要。
以下总结了Java中几种常见的单例模式实现方式,并分析了各自的优缺点。
1. 饿汉式(Eager Initialization)
饿汉式在类加载时就立即创建实例,因此是线程安全的。
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
优点:
- 实现简单,代码可读性高。
- 在类加载时就完成了实例化,避免了多线程同步问题,是线程安全的。
缺点:
- 在类加载时就创建实例,即使后续从未使用过该实例,也会占用内存空间,可能造成资源浪费。
2. 懒汉式(Lazy Initialization)
懒汉式在第一次调用getInstance()方法时才创建实例。
2.1 基础懒汉式(非线程安全)
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
优点:
- 实现了懒加载,只有在需要时才创建实例,节约了资源。
缺点:
- 在多线程环境下是非线程安全的。如果多个线程同时进入
if (instance == null)判断,并且此时instance都为null,那么将会有多个线程创建出多个实例,违背了单例模式的初衷。
2.2 线程安全的懒汉式(同步方法)
通过在getInstance()方法上添加synchronized关键字,可以保证线程安全。
public class SynchronizedLazySingleton {
private static SynchronizedLazySingleton instance;
private SynchronizedLazySingleton() {}
public static synchronized SynchronizedLazySingleton getInstance() {
if (instance == null) {
instance = new SynchronizedLazySingleton();
}
return instance;
}
}
优点:
- 解决了多线程环境下的线程安全问题。
缺点:
- 每次调用
getInstance()方法时都需要进行同步,即使实例已经被创建。这会导致不必要的性能开销,尤其是在高并发场景下。
3. 双重检查锁定(Double-Checked Locking)
双重检查锁定是对线程安全的懒汉式的一种优化,旨在减少不必要的同步开销。
public class DoubleCheckedSingleton {
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {}
public static DoubleCheckedSingleton getInstance() {
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
关键点:
- 使用
volatile关键字修饰instance变量,确保其可见性和禁止指令重排。 - 进行两次
if (instance == null)检查,第一次检查是为了避免在实例已存在的情况下进入同步代码块,从而提高性能。第二次检查是为了在同步代码块内部再次确认实例是否被创建,防止多个线程同时通过第一次检查后重复创建实例。
优点:
- 兼顾了线程安全和懒加载的性能。只有在第一次创建实例时才会进入同步代码块,后续的调用将直接返回已创建的实例,性能较高。
缺点:
- 实现相对复杂,容易出错。
volatile关键字是必不可少的,否则可能由于指令重排导致获取到未完全初始化的对象。
4. 【推荐】静态内部类(Static Inner Class)
这是目前被广泛推荐的一种实现方式,它利用了JVM类加载机制来保证线程安全和懒加载。
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
原理:
- 当
StaticInnerClassSingleton类被加载时,其静态内部类SingletonHolder并不会被加载。 - 只有当第一次调用
getInstance()方法时,JVM才会加载SingletonHolder类,并初始化INSTANCE。 - JVM在加载类时是线程安全的,因此保证了只有一个线程可以初始化
INSTANCE。
优点:
- 实现了懒加载。
- 由JVM保证线程安全,无需任何同步代码,性能高。
- 实现简单,代码清晰。
5. 枚举(Enum)
这是《Effective Java》作者Joshua Bloch推荐的最佳实现方式。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
System.out.println("do something...");
}
}
优点:
- 实现最简单。
- 天然线程安全。
- 能有效防止通过反射和反序列化创建新的对象,从而破坏单例。
缺点:
- 不具备懒加载的特性。
总结
| 实现方式 | 线程安全 | 懒加载 | 推荐指数 | 备注 |
|---|---|---|---|---|
| 饿汉式 | 是 | 否 | ⭐⭐⭐ | 实现简单,但可能造成资源浪费。 |
| 懒汉式(非线程安全) | 否 | 是 | ⭐ | 仅限单线程环境使用。 |
| 懒汉式(同步方法) | 是 | 是 | ⭐⭐ | 性能开销较大,不推荐。 |
| 双重检查锁定 | 是 | 是 | ⭐⭐⭐⭐ | 实现较复杂,需正确使用volatile。 |
| 静态内部类 | 是 | 是 | ⭐⭐⭐⭐⭐ | 兼具懒加载和线程安全,实现简单,推荐使用。 |
| 枚举 | 是 | 否 | ⭐⭐⭐⭐⭐ | 最简洁、最安全的实现方式,能防止反射和反序列化攻击。 |
在实际开发中,静态内部类和枚举是两种最被推崇的单例模式实现方式。如果需要懒加载,静态内部类是绝佳的选择;如果不需要懒加载,或者对防止反射和反序列化有强烈的需求,枚举则是最简单且最安全的方式。
继续阅读
基于全文检索与主题相似度
Android 中监听 Home 键与 Overview 键的实现原理
在 Android 开发中,出于安全和隐私考虑,系统默认不会为 Home 键或 Overview 键(最近任务键)发送全局广播。然而,在某些场景下(如埋点、行为统计、通知触发等),我们仍然需要感知这些按键事件的发生。 本文将详细讲解如何通过 ACTION\_CLOSE\_SYSTEM\_DIALOGS 广播来间接监听这
Android 开发中 applicationId、namespace 以及 package 属性区别和联系
applicationId、namespace 以及 AndroidManifest.xml 中的 package 是 Android 开发中涉及应用标识和代码组织的重要概念。 它们之间既有联系也有区别,尤其是在现代 Android Gradle 插件 (AGP) 版本中,其行为和推荐用法有所演进。 以下是对这三者的详
如何切换 google play 账号到美区,锁定美区实现ChatGPT应用无障碍下载
由于 ChatGPT 在某些国家无法使用, 在 google play 应用商店上对于某些地区也无法下载。一般显示类似“此商品无法在您所在的国家/地区购买或下载”的提示。 这时候你可能想从一些第三方应用商店,比如 APKPure、apkmirror上搜索下载 apk 进行安装,虽然能够成功安装了,但是无法打开运行,打开