实现单例模式
在Java中,单例模式(Singleton Pattern)保证一个类仅有一个实例,并提供全局访问点。这里展示两种常见的实现单例模式的方式:懒汉式(懒加载)和饿汉式(静态初始化)。
1.懒汉式(线程不安全)
这个版本的懒汉式单例在多线程环境下并不安全,可能会创建多个实例。
public class Singleton {
private static volatile Singleton instance;
// 防止通过构造函数直接创建对象
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 这里在多线程环境下可能导致多个实例被创建
}
return instance;
}
}
2.懒汉式(线程安全,使用同步方法)
在这个版本中,我们添加了synchronized关键字来确保多线程环境下的安全性,但这样会影响性能,因为每次调用getInstance()都会进行同步锁操作。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
3.双重检查锁定(线程安全,更优)
双重检查锁定既保证了线程安全,又尽可能地减少了同步的开销。只有在实例未创建时才进行同步,创建后就不再需要同步,提高了效率。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查,避免不必要的同步开销
instance = new Singleton();
}
}
}
return instance;
}
}
4. 饿汉式(静态初始化,线程安全)
饿汉式的实现方式是在类加载时立即创建并初始化单例实例,因此天生就是线程安全的,但无法做到按需加载(lazy initialization)。
public class Singleton {
// 类加载时即初始化实例,保证线程安全
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
以下是第三种实现方式——双重检查锁定(Double-Checked Locking)的Java单例模式代码解释:
public class Singleton {
// 使用volatile关键字确保多线程环境下的可见性和有序性
private static volatile Singleton instance;
// 私有化构造函数以防止外部实例化
private Singleton() {}
// 提供获取实例的方法
public static Singleton getInstance() {
// 第一次检查:如果instance已非空,则直接返回
if (instance == null) {
// 否则同步块内进行第二次检查,并创建实例
synchronized (Singleton.class) {
// 在同步块内部再次检查实例是否已经被其他线程创建过
if (instance == null) {
// 如果未被创建,则创建并初始化
instance = new Singleton();
}
}
}
return instance;
}
}
这段代码的工作原理是这样的:
- 当调用 getInstance() 方法时,首先会进行一个非同步的检查,查看 instance 是否已经初始化。如果已经初始化,则直接返回该实例,无需进入同步块。
- 如果第一次检查发现 instance 为 null,那么代码进入 synchronized 块,在这里进行第二次检查。这是因为在多线程环境下,可能有多个线程同时发现 instance 为空并尝试创建实例,所以需要通过锁来保证只有一个线程能够执行初始化操作。
- 只有在 instance 确实为空的情况下,才会在同步块中创建并初始化 Singleton 类的一个新实例。
- 由于两次检查的存在,可以避免每次调用 getInstance() 方法都进行不必要的同步操作,从而提高了性能。
通过这种方式,我们既保证了单例对象在多线程环境下的唯一性,又尽可能减少了使用同步操作带来的性能开销。