单例模式
单例模式,就是一个类在任何情况下绝对只有一个实例,并且提供一个全局访问点来获取该实例。
要实现单例,至少需要满足两个点:
- 私有化构造方法,防止被外部实例化造成多实例问题
- 提供一个静态方位作为全局访问点来获取唯一的实例对象
在 Java 里面,至少有 6 种方法来实现单例。
实现
第一种
第一种, 是最简单的实现,通过延迟加载的方式进行实例化,并且增加了同步 锁机制避免多线程环境下 的线程安全问题.
java
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null){
instance=new Singleton();
}
return instance;
}
}
但是这种加锁会造成性能问题,而且同步锁只有在第一次实例化的时候才产生作用,后续不需要。
第二种
第二种,通过双重检查锁的方式,减少了锁的范围来提升性能
java
public class Singleton {
private volatile static Singleton instance;
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
instance
使用 volatile
关键字修饰,以确保多线程环境下的可见性和有序性。
使用
volatile
关键字修饰instance
变量,主要是为了保证在多线程环境下获取单例实例的可见性和有序性。具体来说:
可见性:当一个线程第一次访问
getInstance()
方法时,如果instance
为null
,那么该线程将进入同步块并创建实例。这个写操作对于其他线程来说是可见的,即它们将立即看到instance
的新值。这就避免了在一个线程创建实例后,其他线程仍然看到instance
为null
的情况。有序性:在双重检查锁中,由于编译器和处理器的优化行为,可能会发生指令重排序。如果没有使用
volatile
关键字修饰instance
,那么在某些情况下,其他线程可能会看到指令重排后的顺序,从而导致单例实例的未完全初始化。而使用volatile
修饰instance
后,禁止了这种指令重排序优化,保证了实例的完整性。
第三种
第三种,通过饿汉式实现单例。这种方式在类加载的时候就触发了实例化,从而避免了多线程同步问题。
java
public class Singleton {
private static Singleton instance=new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
第四种
第四种,通过在静态块里面实例化,而静态块是在类加载的时候触发执行的,所以也只会执行一
次。
java
public class Singleton {
private static Singleton instance = null;
static {
instance=new Singleton();
}
private Singleton(){}
public Singleton getInstance(){
return instance;
}
}
上面两种方式,都是在类加载的时候初始化,没有达到延迟加载的效果,当然本身影响不大。
第五种
由于静态内部类只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。
java
public class Singleton {
private static class SingletonHolder{
private static final Singleton INSTANCE=new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
所以当 Singleton 被加载的时候不会初始化 INSTANCE,从而实现了延迟加载。
第六种
我们还可以使用枚举类来实现。
java
public enum Singleton {
INSTANCE;
// 添加其他成员变量和方法
public void doSomething() {
// 单例实例的操作
}
}
这种写法既能避免多线程同步问题,又能防止反序列化重新创建新对象,也是一个比较好的方案。
总结
我认为大体分为 3 种方式来实现单例:
-
第一种是通过双重检查锁的方式,它是一种线程安全并且是延迟实例化的方式,但是因为加锁,所以会有性能上的影响。
-
第二种是通过静态内部类的方式实现,它也是一种延迟实例化,由于它是静态内部类,所以只会使用的时候加载一次,不存在线程安全问题。
-
第三种是通过枚举类的方式实现,它既是线程安全的,又能防止反序列化导致破坏单例问题
多线程、克隆、反序列化、反射,都有可能会造成单例的破坏。而我认为,通过枚举的方式实现单例,是能够解决所有可能被破坏的情况。