单例模式
文章目录
- 单例模式
- 定义
- 饿汉式单例
- **饿汉式单例需要加volatile关键字吗**
- 类加载过程
- 懒汉式单列
定义
单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点
饿汉式单例
饿汉式单例是Java中实现单例模式的一种方式,它在类加载时就完成了实例化,因此是线程安全的。以下是饿汉式单例的Java实现:
public class Singleton {// 创建Singleton类的一个对象private static Singleton instance = new Singleton();/* 让构造函数为private,这样该类就不会被实例化 */private Singleton() {}/* 获取唯一可用的对象 */public static Singleton getInstance() {return instance;}public static void main(String[] args) {Singleton singleton1 = Singleton.getInstance();Singleton singleton2 = Singleton.getInstance();System.out.println(singleton1 == singleton2); // 输出:true}
}
- 将构造函数设为private,防止外部直接通过new Singleton()创建实例。
- 在类加载时,静态变量instance就已经初始化,所以它是线程安全的。
- 提供一个公共的静态方法getInstance(),用于获取唯一的实例。
- 在main方法中,我们创建了两个Singleton实例,由于是同一个实例,所以singleton1和singleton2是相等的
饿汉式单例需要加volatile关键字吗
在饿汉式单例中,通常不需要使用volatile关键字。原因如下:
- 线程安全性:饿汉式单例在类加载时就完成了实例化,此时类加载是线程安全的。因此,当多个线程同时访问getInstance()方法时,它们看到的instance引用始终指向同一个实例,不需要额外的同步措施。
- 初始化时机:由于instance在类加载时就已创建,不存在多个线程同时初始化单例的问题,因此不需要volatile来确保内存可见性。
- 不可变性:instance一旦被初始化,就不会再改变,这满足了不可变对象的特性。对于不可变对象,volatile通常是不必要的。
然而,有一种情况可能需要volatile,那就是如果Singleton类中包含一些动态初始化的属性,这些属性在类加载时可能还没有初始化,那么volatile可以确保所有线程都能看到最新初始化的状态。但这种情况并不常见,因为在饿汉式单例中,通常不推荐有动态初始化的属性。
总结来说,标准的饿汉式单例模式在大多数情况下不需要volatile,因为它已经具备了线程安全性和初始化的原子性。
类加载过程
在Java中,类的加载过程是由类加载器(Class Loader)完成的,包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化(Initialization)。在饿汉式单例模式中,instance的创建发生在初始化阶段,具体步骤如下:
1.加载
- 类加载器读取字节码文件(.class文件)并将其加载到JVM中。在这个阶段,JVM并不创建类的实例,只是确保类的信息可以被访问。
2.验证
- 对字节码进行校验,确保它符合Java虚拟机规范,不会对系统造成危害
3.准备
- 这个阶段,JVM为类的静态字段分配内存,并将其初始化为默认值。对于int类型,如instance,默认值是0。但是,instance在这里并不会被实例化,因为它的类型是引用,所以默认值是null。
4.解析
- 类的符号引用(如方法名、字段名)被转换为直接引用,这使得JVM可以直接访问这些实体。
5.初始化
- 这是关键步骤,对于饿汉式单例,初始化时会执行类的静态初始化块(如果有的话)。在饿汉式单例中,instance的初始化就发生在这一阶段。由于instance的初始化表达式new Singleton()是一个静态初始化动作,它会在类初始化时执行,确保instance被创建并赋值为Singleton类的新实例。由于类加载是线程安全的,所以这个过程也是线程安全的。
6.后续使用
- 当其他代码通过Singleton.getInstance()方法请求单例时,由于instance已经在初始化阶段创建,所以直接返回这个已经存在的实例,无需再次创建。
由于类的初始化(包括静态字段的初始化)是线程安全的,饿汉式单例在类加载时创建的实例对所有线程都是可见的,因此它是线程安全的单例实现。
懒汉式单列
懒汉式单例是指在第一次调用获取实例的方法时才创建单例对象,这是一种延迟加载的方式。为了保证线程安全,可以采用同步锁或双重检查锁定(Double-Checked Locking)的方式来实现。下面是使用双重检查锁定的懒汉式单例的Java实现示例:
public class Singleton {// 使用volatile关键字确保多线程环境下的可见性和禁止指令重排序private static volatile Singleton instance;private Singleton() {}public static Singleton getInstance() {// 第一次检查:如果instance不为null,直接返回实例,避免同步if (instance == null) {synchronized (Singleton.class) {// 第二次检查:进入同步块后再次检查,以防多线程下重复创建if (instance == null) {instance = new Singleton();}}}return instance;}public static void main(String[] args) {Singleton singleton1 = Singleton.getInstance();Singleton singleton2 = Singleton.getInstance();System.out.println(singleton1 == singleton2); // 输出:true}
}
- instance声明为volatile,这是为了确保当instance被初始化成Singleton实例之后,其他线程能够立即看到这个变化,同时也能防止指令重排序带来的问题。
- getInstance()方法中首先进行非空检查,如果instance不为null,则直接返回,避免了每次调用都进行同步操作的开销
- 只有当instance为null时,才会进入同步块,这是第二次检查,确保在只有一个线程能够执行到创建实例的代码,从而保证了线程安全。
- 使用Singleton.class作为同步锁,这是因为类加载器保证了每个类只加载一次,因此对应的.class对象在JVM中也是唯一的,可以安全地作为锁对象。
这种实现方式结合了懒加载和线程安全的优点,同时尽量减少了同步操作带来的性能损耗。