线程安全的单例模式是否真的安全
Posted 编程微学堂
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程安全的单例模式是否真的安全相关的知识,希望对你有一定的参考价值。
我看你骨骼惊奇,是个编程的好苗子~
首先,单例模式是我们常用和常见的经典设计模式之一,但是由于在JAVA中,多线程问题往往会引起单例模式的水土不服,所以,我们就见到了线程安全的单例模式,首先一种单例模式如下所示:
在上述代码中,我们对getInstance进行了加锁处理,但是synchronized将导致性能开销,如果存在多个线程频繁的调用getInstance方法,那么将会导致执行性能的下降,所以又出现的一种以“双重检查锁定”为原理的线程安全的单例模式,如下所述:
如代码所示:如果第一次检查对象不为null,则直接返回对象引用,就不需要执行下面的加锁操作,就可以大大的减小性能损耗,这也是我们常见的线程安全的单例模式,但是其实事实是上面的优化是错误的。根本原因发生在对象创建过程中:
为什么这里会有错误呢,原因是这样的:
首先,一个对象的创建需要经历一下三步:
其实如果上面三步顺序执行将不会出现问题,但是由于处理器和编译器存在重排序优化(具体内容会在之后的博客中做阐述),所以将会导致多线程之间的instance引用同步问题。比如下面一种情况就是重排序问题:
所以当线程B来访问:
时,线程B拿到的是一个不为null的instance引用,所以会直接返回,当线程B使用instance引用做处理时,就会出现问题。
那么我们有没有什么方法去解决这个问题呢,答案当然是有的,今天我们只阐述原理较为简单的一种,第二种留待之后讲解。
其实修改很简单,如下:
只需要对instance加volatile锁定就可以了,可以这样理解:
initInstance(memory)
是在对memory 进行写操作。
instance = memory
是在对memory进行读操作,由于对象使用volatile进行了修饰,编译器和处理器对于volatile对象的先写后读是不允许进行重排序的,所以相当于是禁止了重排序问题,重排序问题解决之后就不会出现上面的问题了。所以就可以实现绝对安全的重排序。 事实上,上面的问题出现的几率并不大,因为这种重排序只会在少数的一些JIT编译器上发生,所以其实实际开发中遇到这种情况的场景并不会很多。
以上是关于线程安全的单例模式是否真的安全的主要内容,如果未能解决你的问题,请参考以下文章