1、饿汉式
说明:
汉式单例模式在类加载时就创建实例,因此线程安全,但可能会造成资源浪费。如果实例一直未被使用,就会造成不必要的内存占用。
优点:
- 线程安全(JVM在类加载时初始化INSTANCE)。
- 结构简单,访问速度快。
缺点:
不支持懒汉式加载,如果INSTANCE长时间未使用,就会占用内存。
使用场景:
实例体积较小,程序启动时一定会用到的。
代码示例:
public class Hungry {
private static final Hungry INSTANCE = new Hungry();
private Hungry() {
System.out.println(Thread.currentThread().getName() + "ok");
}
public static Hungry getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
for (int i = 0; i < 5; i++) {
System.out.println(Hungry.getInstance());;
}
}
}
2、懒汉式
说明:
指实例需要时进行创建,而不是在类加载时就进行创建。
优点:
静态内部类方式结合懒汉式加载和线程安全,不会造成性能损耗。
使用场景:
实例对象体积较大时,使用不确定,推荐懒汉式(静态内部类)或者枚举。
代码示例:
public class Lazy {
private Lazy() {
System.out.println(Thread.currentThread().getName() + "ok");
}
private volatile static Lazy lazy;
//双重检测模式的-懒汉单例-dcl
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
/**
* new Lazy()不是原子性操作。
* <p/>
* 1、分配内存空间。
* 2、执行构造方法、初始化对象。
* 3、把这个对象指向这个空间。
*<p/>
*在多线程情况下,由于上面三个步骤可能会出现132,导致其他线程空指针的情况,所以需要volatile 防止指令重排。
*/
}
}
}
return lazy;
}
/**
* @param args
*/
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Lazy lazy1 = Lazy.getInstance();
Lazy lazy2 = Lazy.getInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
3、静态内部类
说明:
结合懒加载和线程安全的有点,被认为最优的单例实现方式之一。
代码示例:
public class Holder {
public Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
原理解析:
- InnerClass 不会在类加载时加载,只会在调用getInstance后加载。
- HOLDER 变量在 InnerClass 类加载时初始化,而JVM 保证了类的加载过程是线程安全的,因此不会有并发问题。
- 由于 SingletonHolder 只有在 getInstance() 方法被调用时才加载,因此实现了懒加载,不会浪费内存。
- 和 synchronized 方式相比,没有额外的同步开销,访问速度更快。
4、枚举
说明:
是最安全、最简洁、最推荐的单例实现方式。它不仅天然线程安全,还能防止反射攻击和序列化破坏。
示例代码:
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance = EnumSingle.INSTANCE;
EnumSingle instance1 = EnumSingle.INSTANCE;
System.out.println(instance);
System.out.println(instance1);
}
}
原理解析:
- JVM 保证线程安全
类型的实例是由 JVM 保证只加载一次,不会出现多线程并发问题。
- 防止反射攻击
反射可以破坏普通单例模式(如 private constructor
),但反射无法创建枚举的实例,因为 Enum类的newInstance()方法会抛出**。
- 防止序列化攻击
- 普通单例类 可能在反序列化时创建新实例,需要手动实现
readResolve()
方法来防止。 - 枚举单例 天然支持序列化,JVM 保证反序列化时不会创建新实例。
5、扩展知识点(反射破坏单例)
说明:
除枚举以外,反射的存在所有单例都是不安全的。
示例代码:
public class Lazy_Reflect {
private static boolean iga = false; //红绿灯字段
private Lazy_Reflect() {
synchronized (Lazy_Reflect.class) { //三层锁机制。
if (iga == false) {
iga = true;
} else {
throw new RuntimeException("不要试图使用反射破坏异常!");
}
}
}
private volatile static Lazy_Reflect lazy;
//双重检测模式的-懒汉单例-dcl
public static Lazy_Reflect getInstance() {
if (lazy == null) {
synchronized (Lazy_Reflect.class) {
if (lazy == null) {
lazy = new Lazy_Reflect();
/**
* new Lazy()不是原子性操作。
* <p/>
* 1、分配内存空间。
* 2、执行构造方法、初始化对象。
* 3、把这个对象指向这个空间。
*<p/>
*在多线程情况下,由于上面三个步骤可能会出现132,导致其他线程空指针的情况,所以需要volatile 防止指令重排。
*/
}
}
}
return lazy;
}
/**
* 反射的存在所有单例都是不安全的,除枚举以外。
*
* @param args
*/
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//反射,获取构造器
Field iga1 = Lazy_Reflect.class.getDeclaredField("iga");
iga1.setAccessible(false);
Constructor<Lazy_Reflect> declaredConstructor = Lazy_Reflect.class
.getDeclaredConstructor(null);
declaredConstructor.setAccessible(false);
Lazy_Reflect lazy2 = declaredConstructor.newInstance();
iga1.set(lazy2,false);
Lazy_Reflect lazy1 = Lazy_Reflect.getInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}