javaee论坛

普通会员

225648

帖子

340

回复

354

积分

楼主
发表于 2019-11-04 06:41:55 | 查看: 118 | 回复: 3

单例设计模式的写法有那么几种,懒汉式和饿汉式,但是相比较而言都不够严谨,存在逻辑漏洞,某些情况下并不能保证完全实现单例,尤其是在并发的情况下,会出现线程不安全的问题,这一点我们这里并不细讲,大家可以自行查找其他文章。

所以双判空加锁的单例写法就出现了,来看看一般人的写法:

publicclassTest{privatestaticTestinstance;privateTest(){}publicstaticTestgetInstance(){if(instance==null){synchronized(Test.class){if(instance==null){instance=newTest();}}}returninstance;}}

乍一看,老铁没毛病,你还想双击666是不?

我们先来分析一下这段代码:

(1)构造函数使用private修饰,保证了其他类调用的时候不能通过这种方式初始化获得对象。

(2)getInstance方法中第一个判空条件,理论上是可以去除的,那为什么要加上呢?因为去掉了以后,不管有没有给instance赋值,都会进行synchronized加锁操作,但是synchronized操作会耗性能,所以第一次instance赋值后判空,避免了每次synchronized操作,提升性能。

(3)synchronized操作是为了避免多线程并发时出现instance多次赋值而达不到单例的效果,加同步后可以避免这个问题。

(4)第二个判空条件就不能去掉了,因为如果线程a和b同时操作,a先获得锁进入了,判空进入了代码,这个时候如果释放了锁b进入了判空条件先一步进行了初始化工作,这个时候a也进行了初始化工作,这就有了两个实例了,不符合要求,所以第二个判空条件不能去掉哦!

好,现在来说一下这种写法的问题:

问题主要在于instance=newSingleton()这句话并不是原子操作,原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

instance=newSingleton()其实做了三件事情:

(1)给instance实例分配内存;

(2)初始化Singleton()成员变量;

(3)将instance对象指向newSingleton()分配的内存空间,所以这个时候instance就不为null了;

问题就出在这儿了,因为JVM中有指令重排序的优化,所以呢正常情况按照1,2,3的顺序来,没毛病,但是也可能按照1,3,2的顺序来,这个时候就有问题了,调用的时候判断instance!=null就直接返回instance实例,但是这个时候并没有进行初始化工作,所以在后续的调用中肯定就会报错了,所以这里引入了volatile修饰符修饰instance对象,因为volatile能够禁止指令重排序的功能,所以能解决我们的这个问题,最后,写出完整+正确的单例双判空写法如下:

publicclassTest{privatevolatilestaticTestinstance;privateTest(){}publicstaticTestgetInstance(){if(instance==null){synchronized(Test.class){if(instance==null){instance=newTest();}}}returninstance;}}

其实如果说写到这儿,正常是没什么问题的,但是如果说非要挑刺儿的话呢,那就是我们被private修饰的构造函数并不安全,依然可以通过反射的方式创建一个新的对象,详情请跳转《Java使用反射创建被private修饰的构造函数对象》查看,感谢!


普通会员

0

帖子

323

回复

351

积分
沙发
发表于 2019-12-12 08:57:00

记录一下

普通会员

0

帖子

315

回复

325

积分
板凳
发表于 2022-12-30 14:35:59

围观

普通会员

0

帖子

363

回复

398

积分
地板
发表于 2024-04-19 00:29:47

围观

您需要登录后才可以回帖 登录 | 立即注册

触屏版| 电脑版

技术支持 历史网 V2.0 © 2016-2017