java设计模式_单例模式中的那些坑

单例模式相信大家都有遇到,面试中出现概率还是比较高的,从最基础的加synchronized锁到双重检查锁,再到加上volatile关键字,

小小的一个单例模式里还是存在不少问题的,下面先看代码:

基础的:


双重检查的:


这一步的改善比较好理解,在多线程的情况下第一个方法的效率会低于第二个方法的,第一个方法会导致每个调用方法的线程都阻塞,直到上个线程结束调用。

而第二个方法只会在对象还没实例化的情况下被阻塞,也就是只要对象被实例化了,后面每个线程都可以第一时间拿到对象无需等待,效率比上面高了不少。

当然了,下面方法synchronized块内部的if判断不能不加,这就是双重检查的意思所在,至于为什么必须要加,这里就不多说了。

大家面试过程中可能会被问到:第二段代码的写法会绝对安全吗,或者说可以保证每个线程都可以成功获取到正确的实例吗。答案是不可以保证

这里就涉及到虚拟机的重排序了,简单说明下:在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影响,虚拟机会按照自己的一些规则将程序编写顺序打乱——即写在后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分地利用CPU。

也就导致了实际代码执行的顺序并不一定是按照我们所看到或者想到的代码顺序来执行的,

这里会发生什么呢,在最终调用构造函数实例化对象前,对象的值可能会被赋值为非空,但是此时的对象状态是错误的,在单线程情况下,就算是这样也无所谓,因为程序总会在调用完构造方法后才返回对象,但是如果在多线程情况下,这个时候对象被其他线程拿到,那就是个状态错误的对象。

所以我们引入了最后一步改善,就是用volatile关键字修饰single变量,

防止重排序是volatile关键字的一个重要作用,我们这里用volatile修饰single变量,上面的情况也就不会发生。

总结:绕来绕去无非是为了保证对象在内存中只存在一个,而且还要获取的效率高,也就面试笔试会这样干了,实际情况中spring较好的解决了这个问题,springIOC中对象默认都是单例的(饿汉式的),spring会在程序开始运行前将指定的对象创建好,也不会存在上面的多线程安全问题了..



猜你喜欢

转载自blog.csdn.net/wb_snail/article/details/78969636