浅析_单例模式
单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
1、饿汉式
特点:类一加载,就初始化了!因为类只会加载一次,故不存在线程安全问题。
public class Hungry {
private static final Hungry HUNGRY = new Hungry();
private Hungry(){}
public static Hungry getInstance(){
return HUNGRY;
}
}
2、懒汉式(DCL)
特点:延迟加载,当需要调用的时候,再初始化!故多线程下存在线程安全问题。
这个比较经典,所以谈谈其中的细节:
(1)为什么双重校验?
第一重校验,是防止没有必要的加锁,避免影响 get 实例的效率;
第二重校验,是防止加锁的过程中,线程A突然阻塞,其他的线程抢先实例化了,而后面线程A被唤醒,导致对象被二次实例化。
(2)为什么要加 volatile 关键字?
因为对象的初始化,大致可以分为三步:
- 分配内存空间
- 执行构造方法,实例化对象
- 把栈帧里定义的对象指向内存空间(栈中存放的是引用地址,只要指向了,就不为null 了)
那么问题就来了:如果指令重排了,第二步初始化最后执行,然后陷入阻塞。那么会有可能会导致,有线程获取的单例对象,其实没有初始化!
public class Lazy {
private volatile static Lazy lazy;
private Lazy(){ }
// 双重检测锁,懒汉式单例,DCL懒汉式
public static Lazy getInstance(){
if(lazy == null){ // 防止非必要加锁
synchronized (Lazy.class){
if(lazy == null){ // 防止有线程在加锁的过程中,对单例对象初始化了
lazy = new Lazy(); // 不是原子性操作
}
}
}
return lazy;
}
}
3、静态内部类
特点:和DCL懒汉式类似,但缺点是 外部无法传递参数 ,具体使用需自行取舍!线程安全是因为它也是利用了类加载的特性。
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
// 延迟加载
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
4、枚举类
特点:最安全的单例,且 不会被反射破坏单例模式!(因为反射源码里有对对象类型进行判断,是否为枚举类)
package com.feng.single;
import java.lang.reflect.Constructor;
/**
* enum 本质也是一个Class 类
* 枚举类实现单例:
* 最安全,反射也没有办法破解它的单例。
*/
public enum EnumSingle {
MY_INSTANCE;
public EnumSingle getInstance(){
return MY_INSTANCE;
}
}
观看源码得知:枚举类才是最安全的单例模式。因为连反射都没办法去构造它的对象,破坏它的单例!
5、总结
以上就是单例的几种实现方式了,就各自的特点来说,DCL懒汉式 和 枚举 是比较适合使用的。