单例模式实现案例
文章目录
- 单例模式实现案例
- **饿汉式**
- **枚举饿汉式**
- **懒汉式**
- **双检锁懒汉式**
- **内部类懒汉式**
单例模式概念: 单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。这对于需要在系统中共享某个资源(例如配置信息、数据库连接池等)的场景非常有用。
饿汉式(Eager Initialization): 在类加载时就创建实例,线程安全,但可能会造成资源浪费。你已经提供了一个经典的饿汉式实现。
懒汉式(Lazy Initialization): 在需要时才创建实例,可能存在线程安全问题,需要进行同步处理。你已经给出了懒汉式和双检锁懒汉式的实现,其中双检锁懒汉式通过双重检查锁定和volatile关键字确保了线程安全。
枚举单例模式: 利用枚举类型的特性,天然防止反射和反序列化攻击。你已经提供了一个枚举饿汉式的实现。
静态内部类单例模式: 使用静态内部类的方式来延迟加载实例,避免了同步问题,也能够天然防止反射和反序列化攻击。你已经给出了一个内部类懒汉式的实现。
为什么 DCL(Double-Checked Locking)中要使用 volatile: 在双检锁懒汉式中,volatile关键字确保了可见性和防止指令重排序。如果没有volatile,在多线程环境中,一个线程可能会看到一个未完全构造的实例,导致不正确的结果。
防止反射攻击: 在饿汉式和懒汉式中,通过在构造方法中加入特定逻辑,可以防止反射攻击。在枚举和静态内部类实现中,由于它们的特性,天然不容易受到反射攻击。
防止反序列化攻击: 在饿汉式和懒汉式中,通过实现readResolve方法可以防止反序列化攻击。在枚举实现中,由于枚举类型的特性,不容易受到反序列化攻击。
要求
- 掌握五种单例模式的实现方式
- 理解为何 DCL 实现时要使用 volatile 修饰静态变量
- 了解 jdk 中用到单例的场景
饿汉式
public class Singleton1 implements Serializable {private Singleton1() {if (INSTANCE != null) {throw new RuntimeException("单例对象不能重复创建");}System.out.println("private Singleton1()");}private static final Singleton1 INSTANCE = new Singleton1();public static Singleton1 getInstance() {return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}public Object readResolve() {return INSTANCE;}
}
- 构造方法抛出异常是防止反射破坏单例
readResolve()
是防止反序列化破坏单例
枚举饿汉式
public enum Singleton2 {INSTANCE;private Singleton2() {System.out.println("private Singleton2()");}@Overridepublic String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}public static Singleton2 getInstance() {return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}
}
- 枚举饿汉式能天然防止反射、反序列化破坏单例
懒汉式
public class Singleton3 implements Serializable {private Singleton3() {System.out.println("private Singleton3()");}private static Singleton3 INSTANCE = null;// Singleton3.classpublic static synchronized Singleton3 getInstance() {if (INSTANCE == null) {INSTANCE = new Singleton3();}return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}}
- 其实只有首次创建单例对象时才需要同步,但该代码实际上每次调用都会同步
- 因此有了下面的双检锁改进
双检锁懒汉式
public class Singleton4 implements Serializable {private Singleton4() {System.out.println("private Singleton4()");}private static volatile Singleton4 INSTANCE = null; // 可见性,有序性public static Singleton4 getInstance() {if (INSTANCE == null) {synchronized (Singleton4.class) {if (INSTANCE == null) {INSTANCE = new Singleton4();}}}return INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}
}
为何必须加 volatile:
INSTANCE = new Singleton4()
不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造- 如果线程1 先执行了赋值,线程2 执行到第一个
INSTANCE == null
时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象
内部类懒汉式
public class Singleton5 implements Serializable {private Singleton5() {System.out.println("private Singleton5()");}private static class Holder {static Singleton5 INSTANCE = new Singleton5();}public static Singleton5 getInstance() {return Holder.INSTANCE;}public static void otherMethod() {System.out.println("otherMethod()");}
}
- 避免了双检锁的缺点
JDK 中单例的体现
- Runtime 体现了饿汉式单例
- Console 体现了双检锁懒汉式单例
- Collections 中的 EmptyNavigableSet 内部类懒汉式单例
- ReverseComparator.REVERSE_ORDER 内部类懒汉式单例
- Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例
大家好,我是xwhking,一名技术爱好者,目前正在全力学习 Java,前端也会一点,如果你有任何疑问请你评论,或者可以加我QQ(2837468248)说明来意!希望能够与你共同进步