## 单例模式(Java) (直接加载, 延迟加载)

单例定义: 确保该类有且仅有一个实例,并提供一个关于它的全局访问点
直接加载: 自始至终全局唯一不可变
延迟加载(懒加载)(load on demand): 外部调用该单例之前单例为空, 第一次外部调用开始以后, 全局唯一不可变

1 直接加载
public class Singleton {
    //	private: 私有,保证封装性,外部只能通过get方法访问,而不能直接访问
    //	static: 静态,保证该成员是属于类的(而不是对象的);如果不写static修饰,表明该成员是属于对象的,不可能是单例
    //	final:该成员不可变
    private static final Singleton INSTANCE = new Singleton();
    
    //	private构造方法, 外部不能调用它, 即不能通过调用构造方法实例化该类
    private Singleton() {
        
    }
    
    //	公有静态get方法,供外部访问私有实例
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
2.0 延迟加载(懒加载(Load On Demand))
public class Singleton {
    
    //	不加final, INSTANCE尚未实例化
    private static Singleton INSTANCE = null;
    
    
    private Singleton() {
        
    }
    
    //	出现外部调用 再实例化
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            //	存在问题:多线程环境下,INSTANCE容易被多次实例化
            INSTANCE = new Singleton();	// 语句1
        }
        return INSTANCE; //	语句2
    }
}
为什么多线程环境下,INSTANCE容易被多次实例化?

举例来说,假设INSTANCE尚未实例化的情况下,有线程A和线程B在同一时段访问getInstance()方法,

第1步:线程A执行语句1,此时线程A获得一个INSTANCE实例,设为x,即INSTANCE==x

第2步:线程B执行语句1,此时线程B获得又一个新的INSTANCE实例,设为y,即INSTANCE==y

第3步:线程A执行语句2,此时 INSTANCE==y

问题:
  1. 线程A应该返回x而不是被替换
  2. INSTANCE是单例应该全局唯一,不能先是x,一会儿又变成y
  3. 线程越多,问题越严重
解决:

​ 只能有起初的一个线程执行new Singleton()生成实例, 所有线程都引用该实例

2.1 延迟加载 适用并发场景: synchronized方法
public class Singleton {
       
    private static Singleton INSTANCE = null;
    
    private Singleton() {
        
    }
    
    //	问题:synchronized属于重量级锁,锁住整个方法(影响范围大)会显著降低并发性能
    public static synchronized Singleton getInstance() {
        if (INSTANCE == null) {
            
            INSTANCE = new Singleton();	
        }
        return INSTANCE; 
    }
}
2.2 延迟加载 改善并发性能 : 双重检测
public class Singleton {
       
    private static Singleton INSTANCE = null;
    
    private Singleton() {
        
    }
    
    //	双重检测
    //	问题:JVM 指令重排 
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized(Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();	
                }
            }
        }
        return INSTANCE; 
    }
}
什么是JVM指令重排?

​ new Singleton()在JVM执行时将其分为三个步骤(三条指令):

memory = allocate(); // 1. 分配对象的内存空间
ctorInstance(memory); // 2. 初始化对象
instance = memory; // 3. 设置instance指向刚分配的内存地址

编译器对指令执行顺序进行优化,改变顺序,例如上述2和3执行顺序互换:

memory = allocate(); // 1. 分配对象的内存空间
instance = memory; // 3. 设置instance指向刚分配的内存地址
ctorInstance(memory); // 2. 初始化对象

还未初始化对象,就将instance指向分配给对象的地址,带入到上一个singleton静态方法中观察:

public class Singleton {
       
    private static Singleton INSTANCE = null;
    
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        if (INSTANCE == null) {	// 语句1
            synchronized(Singleton.class) {
                if (INSTANCE == null) {
                    //	new Singleton() 被执行时细分为如下三步
                    /*memory = allocate();
					instance = memory; 
					ctorInstance(memory); */
                    INSTANCE = new Singleton();	//	语句2
                }
            }
        }
        return INSTANCE; 
    }
}

线程A执行语句2: 执行完了 instance = memory; 尚未执行 ctorInstance(memory);

此时线程B执行语句1, 发现 INSTANCE不为空, 故直接返回, 此时INSTANCE是错误的

2.3 延迟加载 双重检测 同时禁用JVM指令重排: volatile关键字
public class Singleton {
    
    //	volatile 加这里
    private static volatile Singleton INSTANCE = null;
    
    private Singleton() {
        
    }
    
    public static Singleton getInstance() {
        if (INSTANCE == null) {
            synchronized(Singleton.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Singleton();	
                }
            }
        }
        return INSTANCE; 
    }
}

总结: 能用直接加载还是别用懒加载了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值