中午,我和弟弟说:今天看了一种设计模式,感觉和你很搭配!
写代码的弟弟斜眼道:what?
我呵呵一笑说:单身模式啊
弟弟一副惊讶的样子,问单身什么鬼!
我说:单身模式追求永远一个人,在这种模式下,构造器是私有的,实例只能在对象内部创建。
弟弟:这是单例模式,不是单身模式。
我:没错单身模式又有很多种实现方法,经典的有饿汉单身模式、懒汉单身模式、DCL懒汉单身模式(双重检测锁)、枚举单身模式。
弟弟:是单例模式,不是单身模式!饿汉单例模式我知道,在类中使用new创建一个私有的静态的实例对象,然后使用公有静态方法getInstance()让外部能获取这个类。
/**
* 饿汉模式
*
* @author czy
* @date 2021/6/11
*/
public class Hungry {
private Hungry(){
System.out.println("饿汉模式");
}
private static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
我:哎呦!不错哦,你知道的太多了。但是饿汉单身模式饥不择食,加载类的时候就会初始化里面的单例对象,也不看看现在地皮有多贵!
弟弟:再说一遍,是单例模式!所以有了升级版,懒汉单例模式,这种模式下,只创建了单例对象的引用,没有赋值,当有人第一次调用getInstance()获得单例对象的时候才赋值,这样不用就不触发的理念明显节约了不必要的开支。
public class LazyMan {
private LazyMan(){
System.out.println("懒汉单例模式");
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
我:单…
弟弟:你别说话,这样虽然节省了空间,但是在首次有人需要使用单例对象的同时,有多人访问getInstence()获取对象接口,这时大家判断单例对象都是null,于是这些人会分别新建实例,这样就会破坏实例的单一性。
我:加锁啊!基操
弟弟:加做有很多种,可以直接加在获取实例的方法上
public class DCLLzayMan {
private DCLLzayMan(){
System.out.println("双重检测锁的懒汉模式");
}
private static DCLLzayMan dclLzayMan ;
//这种方式太消耗资源。每次调用都要加锁
public static synchronized DCLLzayMan getInstance(){
if (dclLzayMan==null){
dclLzayMan = new DCLLzayMan();
}
return dclLzayMan;
}
}
我:不是良配,开销太大,还不如饿汉模式
弟弟:可以缩小锁的范围,在判断实例为空后,对单例类进行加锁,为了防止多人都走到抢锁环节,需要再判断是否为空,如果为空就new出实例。
public class DCLLzayMan {
private DCLLzayMan(){
System.out.println("双重检测锁的懒汉模式");
}
private static DCLLzayMan dclLzayMan ;
//测试指令从排序,发生概率很小,不要轻易尝试
public static DCLLzayMan getInstance(){
if (dclLzayMan==null){
synchronized (DCLLzayMan.class){
if (dclLzayMan==null){
//不是原子性操作
dclLzayMan = new DCLLzayMan();
}
}
}
return dclLzayMan;
}
}
我:现在安全了?你确定?
弟弟:单线程当然是安全的,但是现在流行多线程,线程多了,就会出幺蛾子!
我们知道new Object()操作不是原子性的,共分三步:
- 为对象分配内存空间
- 初始化对象
- 将内存空间的地址赋值给对应的引用
不巧的是,计算机优化代码时,有种操作叫指令重排,上面操作就变成了:
- 为对象分配内存空间
- 将内存空间的地址赋值给对应的引
- 初始化对象
我们在2执行之后,对象只用内存空间,空间内却没有值。这时候其他人过来获取实例,一看实例已经分配空间了,就是不等于null,就会直接把这个没有初始化的残缺对象拿走使用。严重缺乏安全感!为了解决这个问题,我们请来了打佬volatile,防止创建对象时指令重排序。
public class DCLLzayMan {
private DCLLzayMan(){
System.out.println("双重检测锁的懒汉模式");
}
//volatile:我来禁止指令重排序
private volatile static DCLLzayMan dclLzayMan ;
//测试指令从排序
public static DCLLzayMan getInstance(){
if (dclLzayMan==null){
synchronized (DCLLzayMan.class){
if (dclLzayMan==null){
//不是原子性操作
dclLzayMan = new DCLLzayMan();
}
}
}
return dclLzayMan;
}
}
我:你以为这就安全了?
弟弟:傻瓜,这又不是枚举类怎会安全!我们通过反射技术很容易对单例私有属性关闭校验。就可以为所欲为了,但是枚举类在这方面表象的坚定不移,不会因此被强行破坏单例的单一属性。
给你看看枚举类单例
**
* 无法被反射修改,因此是安全的
*
* @author czy
* @date 2021/6/11
*/
public enum EnumSingle {
SINGLE;
public static EnumSingle getInstance(){
return SINGLE;
}
//送你一个测试方法
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(EnumSingle.getInstance().hashCode());
}).start();
}
}
}
还有,记住了!是单例模式,不是单身模式!