DCL单例模式及如何防御反射破坏单例

     单例模式其实最主要的作用在于,在多线程的情况下只能获取到一个单一的对象,从而避免各种由此引发的问题;

单例模式的3中写法

1.直接在方法上添加synchronized


private static Single instance;
public static synchronized Single getInstance(){
	if(instance == null){
		instance = new Single();
	}
	return instance;

}

     这种方法的缺点是是,每次执行这段代码都要先获取锁,这样一定程度上影响效率;

2.DCL(双检测加锁)

public class Single{

private volatile static Single instance;
private Single(){}

public static Single getInstance(){
if(instance == null ){
  synchronized(Single.class){
  	if(instance == null){
  		instance = new Single();
  	}
  }
}
return instance;
}

}

     DCL相比于上面简单粗暴的直接上锁,有一个好处,就是不用每次获取实例的时候都要上锁;效率在高并发的时候会有一点提升吧;

     解释一下,为什么要做2次的null判断?
     试想一下,这样一个场景:假设现在有5个线程同时执行到这段代码,同时都检测到instance == null;
     他们会竞争锁,但是最终只会有一个线程获得锁,也就是说有4个线程没有得到锁而卡在这儿,等待获得锁的线程执行完这段代码,释放锁之后剩余4个线程继续竞争;
     第一个获得锁的线程得到了这块代码的执行权,肯定会完成instance实例的初始化;而后面的4个线程如果此时不做null判断则会继续给instance赋一个新的Model对象,导致后续的线程和前一个线程获取到的不是一个实例对象,从而出错;
     这就是为什么要双重检测的原因;

这里面其实还有一个问题,就是为什么要在变量前添加volatile;不加会有什么问题?

private volatile Single instance;
volatile关键字有什么作用?

《深入理解java虚拟机第二版》(周志明)的解释
被volatile修饰的变量具备两种特性:
1.保证此变量对所有线程的可见性,这里的可见性是指当前线程修改了这个变量的更新对于其他线程来说是立即可见的,但不是线程安全的;
2,volatile的第二个语义是禁止指令重排序优化,普通的变量仅仅会保证在该方法的执行过程中所有结果正确,但是并不保证变量的赋值操作顺序与代码中的执行顺序一致

在Java实例化的并不是原子操作其实大概有下面几步:

类加载,分配对象空间,初始化对象变量,为对象的变量赋值,返回对象地址给变量

没有使用volatile关键字代码代码可能没有完全的完成对象实例化,就返回了这个实例的地址就可能造成一些意想不到的错误;因此在使用这种双检测加锁时一定要添加volatile关键字;(当然我自己做了几次实验不加这个volatile,也并没有抱任何错误,可能是高并发下偶尔会报错吧)

3.使用DCL的隐患

使用上面的方法创建的单例模式会被反射破坏掉,利用反射获取到构造方法就可以创建实例了,代码如下:


		Class<Single> dclClass = Single.class;
        Constructor<Single> declaredConstructor = dclClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Single refSingle  =declaredConstructor.newInstance();

在构造方法中使用变量来判断,如果instance != null 就报错;这样也是不行的;也可以通过反射来修改instance的值;

		private Single(){
			if(instance != null)throw new RuntimeException(" don't use reflect create object");
		}

攻击代码:

		Class<Single> dclClass = Single.class;
		Field instance = dclClass.getDeclaredField("instance");
		instance.setAccessible(true);
		instance.set(null,null);
		Constructor<Single> declaredConstructor = dclClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Single refSingle  =declaredConstructor.newInstance();

如何防御通过反射机制创建实例对象呢?

1.创建静态内部类

public class HungryMan {
    private HungryMan() {
        if(InnerSingle.single != null){
            throw new RuntimeException(" don't use reflect create object");
        }
    }
    public static HungryMan  getInstance(){
        return InnerSingle.single;
    }
    private static class InnerSingle{
        private static final HungryMan single = new HungryMan();
    }
}

反射修改不了InnerSingle.single的值,因为被final修饰了,初始化之后就不会变;如果通过反射修改也会报错;

2.创建枚举类
public class SingleTest{

	public static Single getInstance(){
		return Single.single;
	}

}


public enum Single{
		single
}

利用枚举类特性,每个实例只会被加载一次天然的适合单例;
为什么enum类只会被加载一次请看:enum详解

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值