前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
单例模式是一种创建型设计模式,其目的是确保类只有一个实例,并且提供全局访问点以访问该实例。
在 Java 中,实现单例模式有多种方式,下面介绍其中的一些。
在饿汉式单例模式中,实例在类加载时就被创建,因此可以保证实例的唯一性。该模式的实现非常简单,可以使用静态初始化器或者私有构造方法来实现。例如:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
在这个例子中, INSTANCE 是一个静态常量,它在类加载时被初始化为 Singleton 类的实例。getInstance()
方法提供了对该实例的全局访问点。
优点:
缺点:
在懒汉式单例模式中,实例在第一次使用时才被创建。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
在上面的实现中,instance 是静态变量,用于存储单例对象。在 getInstance 方法中,如果 instance 为 null,则创建一个新的 Singleton 对象,否则直接返回 instance。由于没有进行同步锁定,所以线程不安全,可能会导致并发创建多个实例的问题。
优点:
缺点:
“双重检查锁”(Double-Checked Locking) 是懒汉式的一种优化,这种实现方式结合了懒汉式和饿汉式的优点,既能够延迟对象的创建时间,又能够保证线程安全。
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;
}
}
在这个例子中, INSTANCE 是一个 volatile 的静态变量,它在第一次使用时被创建。getInstance()
方法使用双重检查锁定来确保 INSTANCE 的唯一性。当多个线程同时访问 getInstance()
方法时,只有第一个线程会获得锁定并创建实例。其他线程会等待锁定被释放后再次检查 INSTANCE 是否为空,从而避免了多次创建实例的情况。
需要注意的是,使用 volatile 关键字可以确保 INSTANCE 变量的可见性和有序性,从而保证多线程环境下的正确性。
优点:
缺点:
枚举单例模式是一种比较新的单例模式实现方式,它在 Java 5 中引入。它利用了 Java 中枚举类型的特性来实现单例模式,枚举类型的每个枚举常量都是单例的。
public enum Singleton {
INSTANCE;
public void doSomething() {
// do something...
}
}
在这个例子中,枚举类型 Singleton 只有一个枚举常量 INSTANCE ,该常量在枚举类型初始化时被创建。由于枚举类型的实例在 Java 虚拟机中是唯一的,因此枚举常量也是单例的。我们可以通过 Singleton.INSTANCE
来访问该实例,并调用 doSomething()
方法。
优点:
缺点:
静态内部类单例模式是一种优雅而简洁的实现方式。它利用了 Java 类加载器的机制来保证实例的唯一性,并避免了饿汉式单例模式的缺点。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
在这个例子中, Singleton 类的构造方法是私有的,只能在 Singleton 类的内部进行调用。 SingletonHolder 类是 Singleton 类的一个静态内部类,它在 Singleton 类被加载时并不会立即被加载,而是在第一次调用 Singleton.getInstance()
方法时才会被加载,从而实现了延迟加载。
由于静态内部类 SingletonHolder 只会被加载一次,因此 INSTANCE 实例也只会被创建一次,从而保证了实例的唯一性。总的来说,静态内部类单例模式是一种比较优秀的单例模式实现方式,它兼顾了线程安全、懒加载和高效等特点,是一种值得推荐的单例模式实现方式。
优点:
缺点:
注册式单例模式是一种灵活而可扩展的实现方式。在该模式中,单例实例被注册到一个全局的注册表中,可以实现对象的统一管理和获取,但需要注意容器的生命周期和线程安全问题。
下面是一个使用 ConcurrentHashMap 实现注册式单例模式的例子:
public class Singleton {
private static Map<String, Singleton> instances = new ConcurrentHashMap<>();
static {
instances.put(Singleton.class.getName(), new Singleton());
}
private Singleton() {}
public static Singleton getInstance() {
return instances.get(Singleton.class.getName());
}
public static void register(String key, Singleton instance) {
instances.put(key, instance);
}
public static Singleton getRegisteredInstance(String key) {
return instances.get(key);
}
}
Spring 框架中的 Bean 注册机制使用的是注册式单例模式。在 Spring 中, Bean 的注册是通过 BeanDefinitionRegistry 接口来完成的,而 BeanDefinitionRegistry 接口的实现类包括了 DefaultListableBeanFactory 和 GenericApplicationContext 等。这些实现类在内部都使用了类似于注册式单例模式的方式来注册和管理 Bean 。
具体来说, Spring 在注册 Bean 时,会将 Bean 的定义信息封装成一个 BeanDefinition 对象,并将其注册到一个全局的 BeanFactory 中,以供后续的使用。在注册 Bean 的过程中, Spring 会根据 BeanDefinition 中的配置信息来创建相应的 Bean 实例,同时还会对其进行依赖注入、生命周期管理等操作。
需要注意的是,Spring 的 Bean 注册机制中,虽然使用了类似于注册式单例模式的方式来管理 Bean ,但它并不是一个完全的单例模式实现。在 Spring 中, Bean 的单例性是在运行时动态实现的,而不是在编译期就确定的。也就是说,如果在 BeanDefinition 中将 scope 属性设置为 prototype ,那么每次获取该 Bean 实例时都会创建一个新的对象,而不是返回同一个单例实例。
优点:
缺点:
ThreadLocal单例模式这种实现方式将单例对象存储在ThreadLocal中,每个线程都有自己的单例对象副本,保证了线程安全。
public class Singleton {
private static final ThreadLocal<Singleton> singletonThreadLocal =
new ThreadLocal<Singleton>() {
@Override
protected Singleton initialValue() {
return new Singleton();
}
};
private Singleton() {}
public static Singleton getInstance() {
return singletonThreadLocal.get();
}
}
在这个示例中,每个线程都有自己的 Singleton 对象副本,使用 ThreadLocal 可以避免线程安全问题。
优点:
缺点:
CAS单例模式这种实现方式利用了CAS原子操作的特性,可以保证线程安全,但需要注意性能问题。
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
Singleton temp = new Singleton();
if (compareAndSetInstance(null, temp)) {
instance = temp;
}
}
}
}
return instance;
}
private static boolean compareAndSetInstance(Singleton expect, Singleton update) {
return Unsafe.getUnsafe().compareAndSwapObject(Singleton.class,
Unsafe.objectFieldOffset(Singleton.class, "instance"), expect, update);
}
}
在这个示例中,使用了 volatile 和 CAS 原子操作来保证线程安全,同时实现了懒加载。需要注意的是,使用 Unsafe 类需要特殊权限,并且 CAS 实现的复杂度比较高,适用于高并发场景。
优点:
缺点:
总之,单例模式是一种非常常用的设计模式,可以确保类只有一个实例,并提供全局访问点以访问该实例。在 Java 中,有多种方式可以实现单例模式,开发者可以根据实际需要选择适合自己的实现方式。