赞
踩
单例模式是一种常见的软件设计模式,其目的是确保一个类在任何情况下只有一个实例,并提供一个全局访问点供外部获取这个唯一的实例。
这种模式特别适用于那些具有全局状态的场合,如配置管理器、线程池、缓存、对话管理等。
命名来源: 懒汉式的名称来自于他“懒惰”的特性。与饿汉式不同,懒汉式单例类在被加载时并不立即创建实例,而是等到第一次被使用时才创建。这就像一个“懒汉”,总是推迟行动,直到不得不行动的时候。
实现特定: 懒汉式的优点是它支持延迟加载,既仅在需要时才创建实例,这样可以节省资源。然而,这种方式需要处理线程安全问题,因为在多线程环境下,如果多个线程同时访问这个类的实例化方法,就可能创建出多个实例,这违背了单例模式的初衷。
实现代码:
public class LazySingleton {
private static LazySingleton instance;
private LazySingletion() {}
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
上面的写法在多线程环境下不安全。
实现代码:
public class SafeLazySingleton {
private static SafeLazySingleton instance;
private SafeLazySingleton() {}
public static synchronized SafeLazySingleton getInstance() {
if (instance == null) {
instance = new SafeLazySingleton();
}
return instance;
}
}
命名来源: 这种方式被称为“饿汉式”是因为它非常“饿”,既在类被加载到内存后就立即实例化单例对象,无论你最终是否需要使用这个实例。这种策略类似于一个“饿汉”,他总是急切地想立即 吃东西,不管是否真正饿了。
实现特点: 饿汉实现简单,因为它在类加载时就完成了实例的初始化,因此也避免了线程同步问题。但它的缺点在于,如果该单例类的实例化过程中需要加载大量资源或执行长时间的初始化,而应用程序又可能并不需要这个实例,那么这种预先加载的方式会导致资源的浪费。
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() {
return instance;
}
}
上面的代码线程安全,但是在类加载时就会创建实例,不是懒加载模式。
“双重检查锁定”(Double-Checked Locking, 简称 DCL)这个名称描述了这种实现模式的关键特性:在创建对象时进行两次条件检查(是否实例已被创建),这两次检查都是为了确保只有一个实例被创建。这个模式的第一次检查发生在同步代码块之外,如果实例已经被创建,就直接返回该实例,避免了进入同步代码块;只有当实力尚未创建时,才进入同步代码块。在同步代码块内部,实现再次检查以确保在等待获取锁的期间实例没有被创建。这种方法确保了单例的线程安全,同时减少了同步带来的性能损耗。
public class DCLSingleton { private static volatile DCLSingleton instance; private DCLSingleton() {} public static DCLSingleton getInstance() { if (instance == null) { // First check (no locking) synchronized (DCLSingleton.class) { if (instance == null) { // Second check (with locking) instance = new DCLSingleton(); } } } return instance; } }
在 Java 中,volatile 关键字的主要作用是确保变量的修改对所有线程立即可见,并防止编译器对带有该关键字的代码进行重排序。在双重检查锁定模式中,volatile 的使用至关重要,原因包括:
没有使用 volatile 时的问题
- 实例化对象的步骤:
步骤1:分配对象内存空间
步骤2:初始化对象
步骤3:设置 instance 指向刚分配的内存地址(此时 instance 不再是 null)- 但是,由于Java内存模型的允许重排序,步骤2和步骤3的顺序可能会交换。这样,其他线程可能会看到一个非null的 instance,但它可能是一个未完全构造的对象。
使用 volatile 的效果
- volatile 关键字防止了指令重排序,确保步骤2(初始化对象)在步骤3(instance 指向内存空间)之前完成。这样,所有看到非null instance 的线程都会看到一个已经初始化的对象。
步骤举例说明
假设有两个线程 A 和 B,它们同时运行 getInstance() 方法:
- 线程A:进入 getInstance() 方法,发现 instance 是 null,进入同步块,再次检查 instance 仍为 null。它开始执行 new Singleton(),由于没有使用 volatile,指令重排序可能发生,导致 instance 先被设置为非 null。线程 A 被中断,未完成初始化。
- 线程B:此时进入 getInstance() 方法,看到 instance 已经不是 null(尽管实例尚未完全初始化),返回这个未完全初始化的对象
这种情况可能导致程序行为出现异常,因此,使用 volatile 是防止此类问题发生的重要机制。
枚举单例模式是使用 Java 的枚举类型来实现单例模式的方法。这种方式不仅能自动支持序列化机制而且通过 JVM 从根本上保证反射攻击的安全问题。
public enum Singleton {
INSTANCE;
public void someServiceMethod() {
System.out.println("Performing a service");
}
}
静态内部类单例模式是通过内部类的特性和类的加载机制来确保单例只被创建一次,同时实现延迟加载和高效性。
getInstance()
方法,从而导致 SingletonHolder.INSTANCE
被引用,这才会导致内部类加载和初始化其静态成员。public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Java实现单例模式主要有以下几种方式:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。