目录
前言
单例模式常见写法有 4 种:饿汉模式、懒汉模式、静态内部类和枚举,接下来我们一一来看。
一、饿汉模式
1.什么是饿汉模式
饿汉模式也叫预加载模式,它是在类加载时直接创建并初始化单例对象,所以它并不存在线程安全的问题。它是依靠 ClassLoader 类机制,在程序启动时只加载一次,因此不存在线程安全问题。
2.代码展示
public class StarvingMode {
// 是线程安全的
// 类加载的时候执行
// JVM 保证了类加载的过程是线程安全的
private static StarvingMode instance = new StarvingMode();
public static StarvingMode getInstance() {
return instance;
}
private StarvingMode() {}
}
3.优缺点
优点:实现简单、不存在线程安全问题。
缺点:类加载时就创建了对象,如果之后没有被使用,就造成了资源浪费的情况。
二、懒汉模式
1.什么是懒汉模式
懒汉模式与饿汉模式相反,只有在第一次被使用的时候,才会被初始化,但是这样会存在线程安全问题。
2.代码展示
public class LazyModeV3 {
private volatile static LazyModeV3 instance = null;
public static LazyModeV3 getInstance() {
// 第一次调用这个方法时,说明我们应该实例化对象了
if (instance == null) {
// 只有 instance 还没有初始化时,才会走到这个分支
// 这里没有锁保护,所以理论上可以有很多线程同时走到这个分支
synchronized (LazyModeV3.class) { // 通过上面的条件,
// 让争抢锁的动作只在 instance
// 实例化之前才可能发生。
// 实例化之后就不再可能
// 加锁之后才能执行
// 第一个抢到锁的线程,看到的 instance 是 null
// 其他抢到锁的线程,看到的 instance 不是 null
// 保证了 instance 只会被实例化一次
if (instance == null) {
instance = new LazyModeV3(); // 只在第一次的时候执行
// 当重排序成 1 -> 3 -> 2 的时候可能出问题
// 通过 volatile 修复
}
}
}
return instance;
}
private LazyModeV3() {}
}
3..注意事项
1.这里使用volatile修饰对象,是为了防止在实例化对象时,发生代码重排序,导致线程不安全。加上volatile关键字时,实例化对象时,不会使其代码重排序。
2.在判断if(instance == null) 时,这里会出现 check-update 场景,会导致他的原子性发生破坏,所以,使用synchronized锁,对其进行保护,以维护他的原子性,从而保证线程安全。
三、静态内部类
1.如何使用静态内部类
静态内部类既能保证线程安全,又能保证懒加载,它只有在被调用时,才会通过ClassLoader机制来加载和初始化内部静态类,因此它是线程安全的。
2.代码展示
public class Singleton {
// 1.防止外部直接 new 对象破坏单例模式
private Singleton() {
}
// 2.静态内部类
private static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
// 3.提供公共获取单例对象的方法
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
四、枚举
1.如何使用枚举
枚举也是在第一次被使用时,才会被 Java 虚拟机加载并初始化,所以它也是线程安全的,且是懒加载的。
2.代码展示
public enum EnumSingleton {
instance;
public EnumSingleton getInstance(){
return instance;
}
}
总结
单例模式适用于经常被访问的对象,或是创建和销毁需要调用大量资源和时间的对象,使用单例模式可以避免频繁创建和销毁对象。单例模式的常用实现方法有 4 种:饿汉模式、懒汉模式、静态内部类和枚举。