15.单例模式


单例模式

单例模式(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)来实现对类的加载,同时采用了一些机制确保类只被初始化一次。

类加载器的工作原理:

  1. 加载(Loading): 类加载器负责将类的字节码加载到内存中。

  2. 链接(Linking): 链接阶段包括验证、准备和解析。其中,验证是确保字节码符合 Java 虚拟机规范,准备是为类的静态变量分配内存并设置默认初始值,解析是将符号引用转换为直接引用。

  3. 初始化(Initialization): 在类初始化阶段,执行类的初始化代码。这个阶段是延迟进行的,即在对类的第一次主动使用时才会触发。

为什么类加载器能够实现一个类只被初始化一次:

  1. 委派模型: Java 的类加载器采用了委派模型,即每个类加载请求都会先委派给父类加载器处理。这样可以保证同一个类不会被多个类加载器重复加载。

  2. 类初始化锁: 在类初始化阶段,类加载器会使用一个互斥锁,确保只有一个线程能够初始化该类。这样可以避免多个线程同时初始化同一个类,保证线程安全性。

  3. 初始化标记: 类加载器在完成对类的初始化后,会在内部使用一个标记,表明该类已经被初始化过。当其他类加载器尝试再次加载同一个类时,会检查这个标记,避免重复初始化。

枚举

枚举方式是 JDK 1.5 之后引入的,它天生就是线程安全的,而且可以防止反射和反序列化攻击。

public enum EnumSingleton {
    INSTANCE;

    // 可以添加其他方法和属性
}
  • 34
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值