DCL,即Double Check Lock,中卫双重检查锁定。其实DCL很多人在单例模式中用过,LZ面试人的时候也要他们写过,但是有很多人都会写错。他们为什么会写错呢?其错误根源在哪里?有什么解决方案?下面就随LZ一起来分析
问题分析
我们先看单例模式里面的懒汉式:
publicclassSingleton{privatestaticSingleton singleton;privateSingleton(){}publicstaticSingleton getInstance(){if(singleton ==null){ singleton =newSingleton();}return singleton;}}
我们都知道这种写法是错误的,因为它无法保证线程的安全性。优化如下:
publicclassSingleton{privatestaticSingleton singleton;privateSingleton(){}publicstaticsynchronizedSingleton getInstance(){if(singleton ==null){ singleton =newSingleton();}return singleton;}}
优化非常简单,就是在getInstance方法上面做了同步,但是synchronized就会导致这个方法比较低效,导致程序性能下降,那么怎么解决呢?聪明的人们想到了双重检查 DCL:
publicclassSingleton{privatestaticSingleton singleton;privateSingleton(){}publicstaticSingleton getInstance(){if(singleton ==null){// 1synchronized(Singleton.class){// 2if(singleton ==null){// 3 singleton =newSingleton();// 4}}}return singleton;}}
就如上面所示,这个代码看起来很完美,理由如下:
- 如果检查第一个singleton不为null,则不需要执行下面的加锁动作,极大提高了程序的性能;
- 如果第一个singleton为null,即使有多个线程同一时间判断,但是由于synchronized的存在,只会有一个线程能够创建对象;
- 当第一个获取锁的线程创建完成后singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象;
通过上面的分析,DCL看起确实是非常完美,但是可以明确地告诉你,这个错误的。上面的逻辑确实是没有问题,分析也对,但是就是有问题,那么问题出在哪里呢?在回答这个问题之前,我们先来复习一下创建对象过程,实例化一个对象要分为三个步骤:
- 分配内存空间
- 初始化对象
- 将内存空间的地址赋值给对应的引用
但是由于重排序的缘故,步骤2、3可能会发生重排序,其过程如下:
- 分配内存空间
- 将内存空间的地址赋值给对应的引用
- 初始化对象
如果2、3发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return的singleton对象是一个没有被初始化的对象,如下:
按照上面图例所示,线程B访问的是一个没有被初始化的singleton对象。
通过上面的阐述,我们可以判断DCL的错误根源在于步骤4:
singleton =newSingleton();
知道问题根源所在,那么怎么解决呢?有两个解决办法:
- 不允许初始化阶段步骤2 、3发生重排序。
- 允许初始化阶段步骤2 、3发生重排序,但是不允许其他线程“看到”这个重排序。
解决方案
解决方案依据上面两个解决办法即可。
基于volatile解决方案
对于上面的DCL其实只需要做一点点修改即可:将变量singleton生命为volatile即可:
publicclassSingleton{//通过volatile关键字来确保安全privatevolatilestaticSingleton singleton;privateSingleton(){}publicstaticSingleton getInstance(){if(singleton ==null){synchronized(Singleton.class){if(singleton ==null){ singleton =newSingleton();}}}return singleton;}}
当singleton声明为volatile后,步骤2、步骤3就不会被重排序了,也就可以解决上面那问题了。
基于类初始化的解决方案
该解决方案的根本就在于:利用classloder的机制来保证初始化instance时只有一个线程。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化。
publicclassSingleton{privatestaticclassSingletonHolder{publicstaticSingleton singleton =newSingleton();}publicstaticSingleton getInstance(){returnSingletonHolder.singleton;}}
这种解决方案的实质是:运行步骤2和步骤3重排序,但是不允许其他线程看见。
Java语言规定,对于每一个类或者接口C,都有一个唯一的初始化锁LC与之相对应。从C到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化阶段期间会获取这个初始化锁,并且每一个线程至少获取一次锁来确保这个类已经被初始化过了。
http://cmsblogs.com/?p=2161
相关推荐
Java内存模型(JMM, Java Memory Model)是Java虚拟机规范的一部分,它规定了程序中各种变量(线程共享变量)的访问规则,以及在并发环境中如何解决因内存可见性问题导致的数据一致性问题。理解JMM是深入学习Java...
总的来说,这篇文章通过实例和分析,揭示了Java DCL模式的一些常见误解,并强调了深入理解JMM对于开发高效、可靠的并发代码的重要性。通过这种方式,开发者可以更好地掌握如何在多线程环境中正确地实现单例模式。
为此,Java引入了一个核心概念——Java内存模型(Java Memory Model, JMM),其目的是规范线程和内存之间的交互规则。 ##### JMM的关键特性 1. **主内存**(Main Memory):所有的变量都存储在主内存中,并被所有...
这些问题都是由于Java内存模型(JMM)中的指令重排序和缓存机制引起的。 为了解决这些问题,我们需要正确地使用同步。同步可以保证线程安全,但是它也会带来性能损失。正确使用同步需要在同一个锁上同步,并且需要...
本文将深入探讨其中的关键概念,如Java内存模型(JMM)、线程通信机制、内存共享以及相关的同步机制。 首先,Java内存模型(JMM)是理解和解决并发问题的基础。JMM规定了线程如何访问和修改共享数据,以确保多线程...
6. **JVM内存模型与并发**:Java内存模型(JMM)规定了线程如何访问和修改共享变量,以及如何确保内存可见性。了解内存模型对于理解和优化并发程序至关重要。 7. **并发编程最佳实践**:包括避免长时间持有锁、减少...
9. **Java内存模型(JMM)**:理解Java内存模型对线程可见性、有序性和原子性的规定,以及`volatile`和`synchronized`如何与JMM协同工作。 10. **性能调优**:如何通过测试和监控来评估并发程序的性能,以及如何...
【JMM(Java内存模型)】 Java内存模型规定了线程如何访问共享变量,以及如何保证线程间的通信。它要求可见性(线程看到其他线程修改过的变量值)、有序性(限制指令重排序),并定义了锁、volatile、final规则来...
最后,书中详细阐述了Java内存模型(JMM)及其对并发的影响,讨论了volatile和synchronized的内存语义。此外,还给出了优化并发程序性能的策略和建议。 总之,《Java并发编程实践》是一本深入浅出的教程,涵盖了...
2. **JVM内存模型**:了解Java内存模型(JMM)和垃圾收集机制,包括堆内存、栈内存、方法区、本地方法栈、程序计数器的划分,以及新生代、老年代、持久代等概念。还要理解各种GC算法,如Minor GC、Full GC、CMS、G1...
8. **JVM内存模型**:Java内存模型(JMM)规定了线程如何共享和访问内存,理解volatile、final以及 Happens-Before原则对于写出正确并发代码至关重要。 9. **并发异常处理**:在并发环境中,异常处理显得尤为重要,...
9. **Java内存模型(JMM)**:JMM定义了线程之间的内存交互规则,是理解并发行为的基础。 10. **Java并发API的演进**:从早期的synchronized和wait/notify到后来的并发集合和并发工具类,了解这些API的发展历程有助...
7. **JVM内存模型与性能优化**:探讨Java内存模型(JMM)对并发的影响,以及如何通过合理设计和优化来提升并发程序的性能。 8. **异常处理与调试**:讲解在并发环境中如何正确处理异常,以及如何使用工具如`jstack`...
6. **Java内存模型**:探讨Java内存模型(JMM)对并发的影响,包括可见性、有序性和原子性。 转向JVM性能调优,课程可能涉及: 1. **JVM内存结构**:理解堆内存(Young、Old、Survivor区)、方法区、栈内存和本地...
9. **Java内存模型**:阐述Java内存模型(JMM)的概念,理解volatile关键字的作用,以及happens-before原则。 10. **并发编程最佳实践**:分享在实际开发中应遵循的并发编程准则,如何编写可维护和可扩展的并发代码...
1. **Java内存模型(JMM)**: JMM定义了线程如何访问共享内存,以及在多线程环境下如何保证数据的一致性和可见性。它规定了重排序规则,但同时也允许为了提高性能进行一定的重排序,只要这些重排序不会影响单线程...
10. **Java内存模型**:理解Java内存模型(JMM)对于编写高效的并发代码至关重要。源码中可能会涉及内存可见性、volatile、synchronized与JMM的关系等。 通过研究这些源码,开发者可以深入理解Java并发编程的原理,...