文章目录
- 单例模式
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 饿汉式(线程安全)
- 双重检查锁定(Double-Checked Locking)
- 静态内部类
- 枚举
单例模式
单例模式(Singleton Pattern) 是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这样的实例通常用于控制对资源的访问,例如数据库连接、线程池、日志对象等。
在单例模式中,类负责创建自己的唯一实例,并提供一个静态方法让外部代码访问该实例。
下面我们来实现单例模式。
懒汉式(线程不安全)
懒汉式是指在第一次调用 getInstance 方法时才会创建实例。这种实现方式在多线程环境下是不安全的。
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {}public static LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}}
懒汉式(线程安全,同步方法)
在懒汉式的基础上,通过添加 synchronized 关键字来保证线程安全。但这种方式会带来性能问题,因为每次获取实例都要进行同步。
public class SynchronizedLazySingleton {private static SynchronizedLazySingleton instance;private SynchronizedLazySingleton() {}public static synchronized SynchronizedLazySingleton getInstance() {if (instance == null) {instance = new SynchronizedLazySingleton();}return instance;}}
饿汉式(线程安全)
饿汉式是指在类加载的时候就创建实例,因此不存在多线程环境下的线程安全问题。但在类加载时就创建实例,可能造成资源浪费。
public class EagerSingleton {private static final EagerSingleton instance = new EagerSingleton();private EagerSingleton() {}public static EagerSingleton getInstance() {return instance;}}
双重检查锁定(Double-Checked Locking)
通过双重检查锁定机制,在懒汉式的基础上进行优化,提高了性能。
public class DoubleCheckedLockingSingleton {private static volatile DoubleCheckedLockingSingleton instance;private DoubleCheckedLockingSingleton() {}public static DoubleCheckedLockingSingleton getInstance() {if (instance == null) {// 第一次检查synchronized (DoubleCheckedLockingSingleton.class) {if (instance == null) {// 第二次检查instance = new DoubleCheckedLockingSingleton();}}}return instance;}}
第一次检查: 主要是为了避免不必要的同步开销。当实例已经创建后,不再需要进入同步块,直接返回已创建的实例。
第二次检查: 在同步块内进行创建实例的操作,如果没有第二次检查,可能会导致多个线程都通过第一次检查,进入同步块,然后其中一个线程创建了实例,而其他线程没有再次检查,也会再次创建实例,破坏了单例的原则。
使用 volatile 关键字修饰 instance 变量,确保多线程环境下的可见性。在 Java 5 及以上的版本,volatile 关键字的语义已经有了明确的规定,可以保证双重检查锁定在多线程环境下的正确性。
静态内部类
通过静态内部类的方式实现单例模式,利用了类加载的机制,保证了线程安全。
public class StaticInnerClassSingleton {private StaticInnerClassSingleton() {}private static class SingletonHolder {private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();}public static StaticInnerClassSingleton getInstance() {return SingletonHolder.instance;}}
类加载时的线程安全性: 类加载过程中,当类被加载到内存中时,类初始化阶段是由 JVM 负责的,它保证了在多线程环境下,一个类只会被初始化一次。这是通过类加载器的锁机制来实现的。
静态内部类的延迟加载: 静态内部类不会在外部类加载时立即被加载,而是在第一次使用时才被加载。由于类加载和初始化是线程安全的,所以静态内部类的加载也是线程安全的。
类加载机制
在 Java 中,类加载器(ClassLoader)是负责加载 Java 类的机制。类加载器的主要任务是将类的字节码加载到内存中,并转换为一个 Class 类型的对象。类加载器通过委托模型(Delegation Model)来实现对类的加载,同时采用了一些机制确保类只被初始化一次。
类加载器的工作原理:
加载(Loading): 类加载器负责将类的字节码加载到内存中。
链接(Linking): 链接阶段包括验证、准备和解析。其中,验证是确保字节码符合 Java 虚拟机规范,准备是为类的静态变量分配内存并设置默认初始值,解析是将符号引用转换为直接引用。
初始化(Initialization): 在类初始化阶段,执行类的初始化代码。这个阶段是延迟进行的,即在对类的第一次主动使用时才会触发。
为什么类加载器能够实现一个类只被初始化一次:
委派模型: Java 的类加载器采用了委派模型,即每个类加载请求都会先委派给父类加载器处理。这样可以保证同一个类不会被多个类加载器重复加载。
类初始化锁: 在类初始化阶段,类加载器会使用一个互斥锁,确保只有一个线程能够初始化该类。这样可以避免多个线程同时初始化同一个类,保证线程安全性。
初始化标记: 类加载器在完成对类的初始化后,会在内部使用一个标记,表明该类已经被初始化过。当其他类加载器尝试再次加载同一个类时,会检查这个标记,避免重复初始化。
枚举
枚举方式是 JDK 1.5 之后引入的,它天生就是线程安全的,而且可以防止反射和反序列化攻击。
public enum EnumSingleton {INSTANCE;// 可以添加其他方法和属性}