单例模式
单例模式(Singleton Pattern)是一种设计模式,目的是确保某个类只有一个实例,并且提供一个全局的访问点。通常在需要控制资源共享、节省内存等场景下使用单例模式。
1. 饿汉式(Eager Initialization)
饿汉式的单例模式在类加载时就创建实例,这样的好处是线程安全的,因为在类加载时实例已经创建好。但是它的缺点是,如果这个单例类没有被使用,还是会创建这个实例,浪费了内存。
代码实现:
public class Singleton {
// 在类加载时就初始化
private static Singleton singleton = new Singleton();
// 私有构造函数
private Singleton() {
System.out.println("生成了一个实例。");
}
// 获取唯一实例
public static Singleton getInstance() {
return singleton;
}
}
优点:
- 线程安全,因为实例是在类加载时就创建的,保证了实例的唯一性。
- 没有加锁,性能较好。
缺点:
- 即使没有使用该实例,类加载时就已经创建了实例,浪费了资源。
2. 懒汉式(Lazy Initialization)
懒汉式单例模式的特点是延迟实例化,只有在第一次调用 getInstance()
时才创建实例。它的缺点是线程不安全,因为多个线程可能会在同一时刻同时通过 if(singleton==null)
判断,从而导致创建多个实例。
代码实现(线程不安全):
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
System.out.println("生成了一个实例。");
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
优点:
- 在类加载时不会创建实例,节省资源。
- 只有在需要时才创建实例,延迟加载。
缺点:
- 线程不安全,在并发情况下可能会创建多个实例。
3. 线程安全的懒汉式(Synchronized)
为了确保多线程环境下的安全性,可以在 getInstance()
方法上加上 synchronized
关键字,避免多个线程同时进入该方法创建多个实例。这样做可以保证线程安全,但加锁会导致性能下降,尤其在并发量大的情况下。
代码实现:
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
System.out.println("生成了一个实例。");
}
// 使用 synchronized 保证线程安全
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
优点:
- 保证了线程安全,适合单线程环境。
缺点:
- 性能开销较大,因为每次调用
getInstance()
方法时都需要获取锁,锁的操作在并发环境中会降低效率。
4. 双重检查锁(Double-Checked Locking)
双重检查锁定通过在 synchronized
块内部再次判断 singleton == null
来避免每次都加锁,提高了性能。第一次判断不加锁,第二次判断时才加锁。
代码实现:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {
System.out.println("生成了一个实例。");
}
public static Singleton getInstance() {
if (singleton == null) { // 第一次检查
synchronized (Singleton.class) {
if (singleton == null) { // 第二次检查
singleton = new Singleton();
}
}
}
return singleton;
}
}
优点:
- 性能更好,只有第一次创建实例时需要加锁,后续调用不需要锁定。
缺点:
- 需要使用
volatile
关键字确保内存可见性,避免多线程环境下的singleton
变量的缓存问题。
5. 静态内部类实现单例(推荐方式)
这种方式利用了 Java 类加载的机制来保证线程安全,同时也能避免不必要的同步开销。它利用了 Java 的类加载机制,只有在 getInstance()
被调用时,SingletonHolder
才会被加载,并且 Singleton
类的实例才会被创建。
代码实现:
public class Singleton {
// 静态内部类,只有在第一次使用时才会被加载
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton() {
System.out.println("生成了一个实例。");
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
优点:
- 延迟加载,只有在调用
getInstance()
时才会创建实例。 - 线程安全,利用了类加载机制保证了线程安全。
- 不需要加锁,提高了性能。
缺点:
- 这种方式依赖于 JVM 的类加载机制,较为复杂,但通常推荐使用这种方式,因为它是线程安全且高效的。
总结:
- 饿汉式:线程安全,但会在类加载时就创建实例,浪费资源。
- 懒汉式:延迟加载,避免资源浪费,但线程不安全。
- 线程安全的懒汉式:使用
synchronized
保证线程安全,但性能较差。 - 双重检查锁:优化了线程安全,避免了每次加锁,但代码较复杂。
- 静态内部类:推荐的单例实现方式,线程安全且性能高。
根据实际需求选择合适的单例实现方式。通常推荐使用 静态内部类实现单例,因为它既线程安全又高效。