单例模式实现案例

单例模式实现案例

文章目录

  • 单例模式实现案例
    • **饿汉式**
    • **枚举饿汉式**
    • **懒汉式**
    • **双检锁懒汉式**
    • **内部类懒汉式**

单例模式概念: 单例模式是一种创建型设计模式,确保一个类只有一个实例,并提供全局访问点。这对于需要在系统中共享某个资源(例如配置信息、数据库连接池等)的场景非常有用。

饿汉式(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()");
    }

    @Override
    public 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.class
    public 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)说明来意!希望能够与你共同进步

你可能感兴趣的:(Java面试题,单例模式,java,开发语言)