本篇文章为大家展示了double-checked locking与单例模式的示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
单例模式有如下实现方式:
这种方式称为延迟初始化,但是在多线程的情况下会失效,于是使用同步锁,给getInstance() 方法加锁:
同步是需要开销的,我们只需要在初始化的时候同步,而正常的代码执行路径不需要同步,于是有了双重检查加锁(DCL):
这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。
指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。例如 instance = new Singleton() 可分解为如下伪代码:
但是经过重排序后如下:
将第2步和第3步调换顺序,在单线程情况下不会影响程序执行的结果,但是在多线程情况下就不一样了。线程A执行了instance = memory(这对另一个线程B来说是可见的),此时线程B执行外层 if (instance == null),发现instance不为空,随即返回,但是得到的却是未被完全初始化的实例,在使用的时候必定会有风险,这正是双重检查锁定的问题所在!
鉴于DCL的缺陷,便有了修订版:
修订版试图引进局部变量和第二个synchronized来解决指令重排序的问题。但是,Java语言规范虽然规定了同步代码块内的代码必须在对象锁释放之前执行完毕,却没有规定同步代码块之外的代码不能在对象锁释放之前执行,也就是说 instance = temp 可能会在编译期或者运行期移到里层的synchronized内,于是又会引发跟DCL一样的问题。
在JDK1.5之后,可以使用volatile变量禁止指令重排序,让DCL生效:
volatile的另一个语义是保证变量修改的可见性。
单例模式还有如下实现方式:
这种方式称为延迟初始化占位(Holder)类模式。该模式引进了一个静态内部类(占位类),在内部类中提前初始化实例,既保证了Singleton实例的延迟初始化,又保证了同步。这是一种提前初始化(恶汉式)和延迟初始化(懒汉式)的综合模式。
至此,正确的单例模式有三种实现方式:
1.提前初始化。
2.双重检查锁定 + volatile。
3.延迟初始化占位类模式。
上述内容就是double-checked locking与单例模式的示例分析,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。