懒汉 双重检测锁定法
public class Singleton {
//声明成 volatile 防止指令重排
private volatile static Singleton instance;
private Singleton (){
//todo
}
public static Singleton getSingleton() {
if (instance == null) { //真的null再来 synchronized
//锁范围开始
synchronized (Singleton.class) {//这个加外面太浪费了
if (instance == null) {// 双重检测
//instance 是volatile
instance = new Singleton();
}
}//锁范围结束
}
return instance;
}
}
2次null检测的原因
if (instance == null) {
//如果这里等着好几个线程
synchronized (Singleton.class) {
如果有好几个线程 第一次检测是null, 都要来new这个唯一对象出,
他们会等在这个锁这里
第二次null检测没有的话
他们会各自拿到自己new的对象离去, 根本不单例了
instance 用volatile 修饰的原因
instance 如果没用volatile 修饰,
instance = new Singleton();
这句会有问题
因为这样看源码是原子操作, 只有一句, 实际上有3句
1、memory = allocate() 分配对象的内存空间
2、ctorInstance() 初始化对象
3、instance = memory 设置instance指向刚分配的内存
23是可能被互换顺序的, 如果互换了, 这个时候instance已经写入了对对象的引用, 不是null了, 这个时候来获取单例, 会被直接拿走, 但是这个对象 因为2还没来得及执行, 是没初始化的(就是构造方法没执行), 可能会产生错误
注意 : 这里虽然在syn{}范围内 ,syn不阻塞读, 中间过程是可以被看到的, 会有脏读的问题,这里是用volatile +双重null判断 换取了不用整个
getSingleton
方法都syn
缺点: 太复杂了, 人容易出错
饿汉模式
jvm保证类的初始化, 只会一个线程进行, 只进行一次
public class Singleton{
//类加载时就初始化
private static final Singleton instance = new Singleton();
private Singleton(){
//todo
}
public static Singleton getInstance(){
return instance;
}
}
缺点: 类初始化耗时,最终不用就浪费了
看类初始化的几个常见情况,还是有可能没用就类初始的:
× new 出来
√ 访问其static成员
× 反射
× 子类初始化
× defealt方法
×main方法所在类
× 动态语言
存在其他static字段或者方法时,访问其他static成员的时候就会触发了 不用等getInstance
, 就是这里可能有浪费
改进饿汉模式
@Slf4j
public class InnerClassSingleton {
static class Holder {
static InnerClassSingleton instance;
static {
log.info("init singleton");//后
instance=new InnerClassSingleton();
}
}
private static InnerClassSingleton getInstance() {
return Holder.instance;
}
public static void main(String[] arg){
log.info("start getInstance");//先
InnerClassSingleton s= InnerClassSingleton.getInstance();
}
}
就是在里面再包了一层内部类, 单例对象是作为内部类的static field, 内部类的初始时机就可以 用外类(就是单例的类)控制了, 不会有没getInstance
就生成的情况了
枚举模式
最好的模式
即不像懒汉那么复杂,又不像饿汉一样浪费
public static enum Singleton {
INSTANCE;
// 私有构造器
Singleton() {
//.....
}
}
要拿的时候
Singleton.INSTANCE
这样拿就行了
JVM 会保证第一次拿的时候, 才去初始化(不像饿汉浪费)
其实 枚举本身 就是JVM给我实现的单例