前言
单例模式各种实现的讲解,为什么双检要加volatile?
单例模式
顾名思义就是只有一个对象实例,对外提供一个public方法获取这个实例
单例模式有七种实现方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
步骤:
- 私有化构造器,防止外部new
- 内部创建静态常量对象
- 对外提供public static方法获取实例
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
优点:类加载时就创建对象,且只会有一次创建,没有线程安全问题。
缺点:类加载时就创建对象,类加载时机有很多种,并不一定会用到该对象,没有达到lazy loading效果,会造成内存浪费。
饿汉式(静态代码块)
public class Singleton {
private static final Singleton instance;
private Singleton() {}
static {
instance = = new Singleton();
}
public static Singleton getInstance() {
return instance;
}
}
优缺点同上
懒汉式(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:起到了lazy loading效果
缺点:线程不安全
懒汉式单例模式的实现需要考虑线程安全性,可以通过 synchronized 关键字或者双重检查锁定等方式来实现。
懒汉式(线程安全,同步方法)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
优点:起到了lazy loading,解决了线程安全问题
缺点:效率太低,每次访问都要等待锁,其实只需要第一次创建时加锁就可以了,后续直接返回即可
双重检查
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;
}
}
这样就解决了懒加载问题,线程安全问题,也解决了效率问题。
总之,使用枚举方式实现单例模式是一种简单、高效双检为什么加volatile
双创检查需要volatile是为了禁止指令重排,new一个对象首先会分配内存空间,然后赋值初始化,在单线程情况下不会有问题,但是多线程情况下就可能因为编译器的指令重排导致我线程A在分配完内存空间后,线程B的指令重排导致在线程A还没完成对象初始化就开始执行外部的instance == null,这个情况下instance是不为null的,那就会获取到instance这个对象,但是这个对象只是分配了内存并没有完成初始化,属性都是为null的,就可能出现意想不到的问题,例如空指针。
加了volatile就保证了线程B的判断null的指令在我线程A完成instance初始化后才执行。
静态内部类
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
在这个例子中,我们定义了一个私有的静态内部类 SingletonHolder,这个内部类中定义了一个私有的静态变量 INSTANCE,它是 Singleton 的唯一实例。在 getInstance() 方法中,我们返回 SingletonHolder.INSTANCE,这样就可以获取到 Singleton 的唯一实例。
这种方式的优点是,由于静态内部类只有在被调用时才会被加载,因此可以实现懒加载的效果;同时,由于静态内部类只会被加载一次,因此可以保证线程安全。此外,使用静态内部类实现单例模式的代码比较简洁,也比较易于理解和维护。
枚举
使用枚举方式实现单例模式是一种非常简单、高效的方式,而且天然地保证了线程安全和防止反射攻击。
下面是一个使用枚举方式实现单例模式的示例代码:
public enum Singleton {
INSTANCE;
public void doSomething() {
// 单例对象的具体操作
}
}
在这个例子中,我们定义了一个枚举类型 Singleton,并且在其中定义了一个 INSTANCE 实例,它是 Singleton 的唯一实例。由于枚举类型的特殊性质,它保证了实例只会被实例化一次,并且在多线程环境下也是安全的。我们可以通过 Singleton.INSTANCE 来获取单例对象。
另外,使用枚举方式实现单例模式还有一个很大的优点,就是可以防止反射攻击。由于枚举类型的构造函数默认是私有的,因此无法通过反射来创建枚举实例,从而保证了单例的唯一性。
总之,使用枚举方式实现单例模式是一种简单、高效、线程安全并且防止反射攻击的方式,可以考虑在实际开发中使用。