在软件工程中,单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点。在Java中,实现单例模式有多种方式,每种方式都有其特点和适用场景。以下是Java中单例模式的一些常见实现方式。
1. 懒汉式(线程不安全)
这是最基本的实现方式,只在实例被首次使用时才初始化。
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
2. 懒汉式(线程安全)
在多线程环境中,如果两个线程同时检测到实例为null
,那么会创建两个实例。为了避免这种情况,我们可以使用synchronized
关键字来确保线程安全。
public class LazySingletonThreadSafe {
private static volatile LazySingleton instance;
private LazySingletonThreadSafe() {}
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingletonThreadSafe();
}
return instance;
}
}
3. 饿汉式
饿汉式在类加载时就完成了初始化,保证了线程安全,但是可能会浪费资源,因为实例可能永远不会被使用。
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
4. 双重检查锁定(Double-Checked Locking)
双重检查锁定是一种在保证线程安全的同时,减少性能消耗的方法。它利用了volatile
关键字和synchronized
来确保只有一个线程能够创建实例。
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;
}
}
5. 静态内部类
这种方式利用了Java的类加载机制来实现单例,是线程安全的,并且实现了延迟加载。
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton() {}
private static class SingletonHolder {
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6. 枚举
使用枚举实现单例是最简单的方式,也是最推荐的方式之一。枚举值在Java中是唯一的,因此可以保证单例的唯一性。此外,Java虚拟机会保证每个枚举常量只会被实例化一次。
public enum EnumSingleton {
INSTANCE;
public void doSomething() {
// ...
}
}
总结
单例模式在Java中有多种实现方式,每种方式都有其适用场景。在选择实现方式时,需要考虑是否需要考虑线程安全、是否需要延迟加载、是否需要全局访问等因素。枚举和静态内部类是推荐的方式,因为它们简单、易用且在大多数情况下足够好。然而,在某些特定场景下,其他方式可能更加适用。