单例模式(饿汉+懒汉)

单例模式

定义

单例模式(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
    }
}
  1. 将构造函数设为private,防止外部直接通过new Singleton()创建实例。
  2. 在类加载时,静态变量instance就已经初始化,所以它是线程安全的。
  3. 提供一个公共的静态方法getInstance(),用于获取唯一的实例。
  4. 在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
    }
}
  1. instance声明为volatile,这是为了确保当instance被初始化成Singleton实例之后,其他线程能够立即看到这个变化,同时也能防止指令重排序带来的问题。
  2. getInstance()方法中首先进行非空检查,如果instance不为null,则直接返回,避免了每次调用都进行同步操作的开销
  3. 只有当instance为null时,才会进入同步块,这是第二次检查,确保在只有一个线程能够执行到创建实例的代码,从而保证了线程安全。
  4. 使用Singleton.class作为同步锁,这是因为类加载器保证了每个类只加载一次,因此对应的.class对象在JVM中也是唯一的,可以安全地作为锁对象。

这种实现方式结合了懒加载和线程安全的优点,同时尽量减少了同步操作带来的性能损耗。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值