单例模式 #
Singleton
实现方法 #
- 初始化时即创建单例
- 饿汉式
- 枚举类型
- 按需,延迟创建单例
- 懒汉式
- 基础实现
- 同步锁
- 双重检验锁
- 静态内部类实现
- 懒汉式
饿汉式(线程安全) #
- JVM 在类的初始化阶段 (即 在 Class 被加载后、被线程使用前),会执行类的初始化
- 在执行类的初始化期间,JVM 会去获取一个锁,这个锁可以同步多个线程对同一个类的初始化
class Singleton {
// 1. 加载该类时,单例就会自动被创建
private static Singleton ourInstance = new Singleton();
// 2. 构造函数设置为 私有权限,禁止他人创建实例
private Singleton() {
}
// 3. 通过调用静态方法获得创建的单例
public static Singleton newInstance() {
return ourInstance;
}
}
应用场景
- 单例对象要求初始化速度快,占用内存小
枚举类(线程安全) #
- 这是最简洁,最易用的单例实现方式
单元素的枚举类型已经成为实现 Singleton 的最佳方法 - 《Effective Java》
public enum Singleton {
// 定义 1 个枚举的元素,即为单例类的1个实例
INSTANCE;
// 隐藏了 1 个空的、私有的 构造方法
// private Singleton () {}
}
// 获取单例的方式:
Singleton singleton = Singleton.INSTANCE;
懒汉式 基础实现(线程不安全) #
class Singleton {
// 1. 类加载时,先不自动创建单例,将单例的引用先赋值为 Null
private static Singleton ourInstance = null;
// 2. 构造函数 设置为 私有权限
// 禁止他人创建实例
private Singleton() {
}
// 3. 需要时才手动调用 newInstance() 创建 单例
public static Singleton newInstance() {
// 先判断单例是否为空,以避免重复创建
if (ourInstance == null) {
ourInstance = new Singleton();
}
return ourInstance;
}
}
懒汉式 同步锁(线程安全) #
// 写法1
class Singleton {
// 1. 类加载时,先不自动创建单例
// 即,将单例的引用先赋值为 Null
private static Singleton ourInstance = null;
// 2. 构造函数 设置为 私有权限
// 原因:禁止他人创建实例
private Singleton() {
}
// 3. 加入同步锁
public static synchronized Singleton getInstance() {
// 先判断单例是否为空,以避免重复创建
if (ourInstance == null)
ourInstance = new Singleton();
return ourInstance;
}
}
// 写法2
// 该写法的作用与上述写法作用相同,只是写法有所区别
class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
// 加入同步锁
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
缺点
- 每次访问都要进行线程同步(即 调用 synchronized 锁),造成过多的同步开销(加锁 = 耗时、耗能)
- 实际上只需在第 1 次调用该方法时才需要同步,一旦单例创建成功后,就没必要进行同步
懒汉式 双重检验锁(线程安全) #
class Singleton {
private static Singleton ourInstance = null;
private Singleton() {
}
public static Singleton newInstance() {
// 加入双重校验锁
// 校验锁1:第1个if
if (ourInstance == null) { // ①
synchronized (Singleton.class) { // ②
// 校验锁2:第2个 if
if (ourInstance == null) {
ourInstance = new Singleton();
}
}
}
return ourInstance;
}
}
// 说明
// 校验锁1:第1个if
// 作用:若单例已创建,则直接返回已创建的单例,无需再执行加锁操作
// 即直接跳到执行 return ourInstance
// 校验锁2:第2个 if
// 作用:防止多次创建单例问题
// 原理
// 1. 线程A调用newInstance(),当运行到②位置时,此时线程B也调用了newInstance()
// 2. 因线程A并没有执行instance = new Singleton();,此时instance仍为空,因此线程B能突破第1层 if
// 判断,运行到①位置等待synchronized中的A线程执行完毕
// 3. 当线程A释放同步锁时,单例已创建,即instance已非空
// 4. 此时线程B 从①开始执行到位置②。此时第2层 if 判断 = 为空(单例已创建),因此也不会创建多余的实例
缺点
- 实现复杂(多种判断),易出错
静态内部类(线程安全) #
class Singleton {
// 1. 创建静态内部类
private static class Singleton2 {
// 在静态内部类里创建单例
private static Singleton ourInstance = new Singleton();
}
// 私有构造函数
private Singleton() {
}
// 延迟加载、按需创建
public static Singleton newInstance() {
return Singleton2.ourInstance;
}
}
调用过程说明:
- 外部调用类的 newInstance()
- 自动调用 Singleton2.ourInstance
- 此时单例类 Singleton2 得到初始化
- 而该类在装载 & 被初始化时,会初始化它的静态域,从而创建单例;
- 由于是静态域,因此只会 JVM 只会加载 1 遍,Java 虚拟机保证了线程安全性
- 最终只创建 1 个单例
缺点
- 单例类的职责过重,里面的代码可能会过于复杂,在一定程度上违背了 “单一职责原则”
- 如果实例化的对象长时间不被利用,会被系统认为是垃圾而被回收,这将导致对象状态的丢失
代码示例 #
叶王 © 2013-2024 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。