来源:《The "Double-Checked Locking is Broken" Declaration》
1. 单例模式的简单实现
// 只支持单线程的版本 class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) helper = new Helper(); return helper; } }
在多线程的情况下,可能会生成多个Helper实例。
2. 同步getHelper方法
// 支持多线程的版本 class Foo { private Helper helper = null; public synchronized Helper getHelper() { if (helper == null) helper = new Helper(); return helper; } }
getHelper()方法被标记为synchronized后,JVM会只允许同时只有一个线程能执行getHelper方法。所以不会产生多个Helper实例。但是同步的开销会导致多线程执行效率降低。
3. 一种错误的双检锁方法
// 一种错误的双检锁方法 class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } }
为什么是错的?
原因:在 helper = new Helper(); 这句中有两个主要操作。一是创建Helper的一个实例,二是将这个新创建的Helper实例的引用赋给helper这个字段。编译器规定这两个操作的顺序可能是先赋值实例的引用,后执行Helper实例内部的初始化构建。这可能导致另一线程在调用getHelper()方法时,因为helper字段不为null,所以开始使用helper所指的实例,而该实例的初始化工作却仍在前一线程中处于未完成状态,这就导致第二个线程使用了一个“坏”的Helper。
即使编译器事先规定了先构建实例后赋值,在一个多处理器系统中,处理器和内存系统还是有可能颠倒这两个操作的顺序。
4. 另一种错误的双检锁方法
// 另一种错误的双检锁方法 class Foo { private Helper helper = null;wei public Helper getHelper() { if (helper == null) { Helper h; synchronized(this) { h = helper; if (h == null) synchronized (this) { h = new Helper(); } // 释放内部的 synchronized 锁 helper = h; } } return helper; } }
为什么是错的?
原因:退出监控(monitorexit)(如释放 sychronized 锁)的规则是“在 monitorexit 前的操作必须在释放锁之前执行”。但是没有规则保证“在 monitorexit 后的操作必须在释放锁之后才能执行”。也就是说编译器可能会把 helper = h; 这句移到内部的 synchronized 代码块内。这就又回到了3中的情况,即其它线程可能拿到一个“坏”的未完工的Helper实例。
注:在.Net CLR中情况有所不同。在CLR中,任何锁方法的调用都构成了一个完整的内存栅栏,在栅栏之前写入的任何变量都必须在栅栏之前完成;在栅栏之后的任何变量读取都必须在栅栏之后开始。
同步用的越多,越有可能导致性能问题,也增大了出错的可能。
另外每个处理器都缓存了的变量值的备份,在某些类型的处理器中,即使其它处理器利用内存栅栏(memory barriers)将新值写入了共享内存,因为处理器用的是它自己的备份值,还是会认为helper值为null,导致新建了Helper实例,并使用了这个新实例。(Alpha处理器)
5. 可以对32位的原始类型数据变量用双检锁(如int和float)
因为原始类型数据的变量存的就是值本身,所以对它赋值就直接改了变量内容。但是64位的原始类型数据变量,如long和double,就无法保证该操作的原子性。
class Foo { private int cachedHashCode = 0; public int hashCode() { int h = cachedHashCode; if (h == 0) synchronized(this) { if (cachedHashCode != 0) return cachedHashCode; h = computeHashCode(); cachedHashCode = h; } return h; } }
6. 利用volatile的双检锁方法
从JDK5开始,volatile严格地限制了变量的读写顺序,不允许重排。
class Foo { private volatile Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (helper == null) helper = new Helper(); } } return helper; } }
对不可变对象(Immutable Objects)引用的读写都是原子性的操作。所以如果Helper是不可变对象,如String和Integer,可以不用volatile关键字。
7. 总结:
尽量参考使用已有的最佳实践,不要自己去发明。
一门合适的编程语言应该是优雅的。如果发现已有的问题很难用优雅的方式解决,要么换一种语言,要么发出声音,让开发这门语言的人从源头改进该语言。
对于绝大多数只是使用语言的你,不要为了研究语言而研究,应该基于现有的业务问题去研究。多接触各种业务,自然就会多遇到各种问题,自然有机会学得更多。
相关推荐
在Java并发编程中,双检锁(Double-Checked Locking)是一种用于减少同步开销的优化技术,尤其适用于懒加载(lazy initialization)的场景。本文将详细探讨双检锁的工作原理、潜在问题以及如何安全地实现它。 双检锁...
在努力创建更有效的代码时,Java 程序员们创建了双重检查锁定习语,将其和单例创建模式一起使用,从而限制同步代码量。然而,由于一些不太常见的 Java 内存模型细节的原因,并不能保证这个双重检查锁定习语有效。 ...
- **双检锁/双重校验锁(DCL)模式** 用于安全地初始化单例对象,确保在多线程环境下正确创建。 5. **并发集合** - **线程安全的集合类** 如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,它们在内部实现了...
9. **并发设计模式**:如生产者消费者模式、读写锁模式、双检锁/双重校验锁(DCL)模式、线程池模式等,都是在并发编程中常用的设计模式,有助于解决并发问题并提高代码的可维护性。 10. **线程池**:Java的...
10. **并发设计模式**:例如生产者消费者模式、读写锁策略、双检锁/双重校验锁(DCL)等,这些都是解决并发问题的经典模式。 以上知识点是Java并发编程书籍通常会涵盖的内容,通过深入学习和实践,开发者能够编写出...
5. **高性能设计模式**:包括生产者消费者模型、读写锁策略、双检锁/双重校验锁(DCL)、线程局部变量(ThreadLocal)等,这些都是解决高并发问题时常用的设计模式。 6. **JVM内存模型与并发**:Java内存模型(JMM)...
3. **并发设计模式**:书里可能讨论了如何使用各种并发设计模式来解决并发问题,比如生产者消费者模型、双检锁(DCL)、读写锁(ReentrantReadWriteLock)等。 4. **并发工具类**:Java并发包(java.util....
5. **并发设计模式**:书中列举了多种并发设计模式,如生产者消费者模型、读写锁策略、双检锁/双重校验锁(DCL)等,这些都是解决并发问题的常用手段。 6. **死锁、活锁与饥饿**:分析了这三种并发问题的原因及避免...
8. **并发编程模式**:书中还讨论了一些经典并发编程模式,如生产者-消费者模型、双检锁(DCL)和工作窃取算法等。 9. **源码分析**:jcip-examples-src.jar包含了书中示例的源代码,读者可以通过阅读和运行这些...
5. **线程安全的类与设计模式**:分析了如何设计和实现线程安全的类,以及一些常见的线程安全设计模式,如双检锁/双重校验锁(DCL)、静态工厂和单例模式的并发实现。 6. **性能调优与诊断**:讲解了如何评估并发...
7. **并发设计模式**:书中也会涵盖一些经典的并发设计模式,如生产者消费者模型、读写锁、双检锁等,这些都是解决并发问题的有效工具。 8. **并发异常处理**:在并发环境下,异常处理变得更为复杂,书中有专门章节...
7. **并发设计模式**:书中可能涵盖了生产者消费者模型、双检锁/双重校验锁(DCL)、线程中断、线程组等经典并发设计模式,这些都是解决并发问题的有效策略。 8. **实战演练**:书中提供的源代码涵盖了上述所有知识点...
8. **并发设计模式**:介绍了生产者消费者模式、读写锁模式、双检锁/双重校验锁(DCL)模式等并发设计模式,以及如何在实际项目中应用这些模式。 9. **并发异常处理**:讨论了在并发环境中如何有效地捕获和处理异常...
9. **并发编程模式**:如生产者消费者模型、读写锁策略、双检锁/双重校验锁(DCL)等,这些模式有助于解决特定的并发问题。 通过深入学习"java高级并发编程32例"中的案例,开发者可以更好地掌握如何在实际项目中...
2. **单例模式(Singletons)**: 书中讨论了几种实现单例的方式,包括懒汉式、饿汉式、双检锁/双重校验锁(DCL)和静态内部类方式,对比了它们的优缺点和线程安全问题。 3. **构造函数与工厂方法(Constructors and...
5. **并发编程模式**:介绍如生产者-消费者模型、读写锁模式、双检锁/双重校验锁(DCL)等经典并发编程模式,以及如何在Java 9中实现这些模式。 6. **并行流与函数式编程**:Java 9引入的并行流使得开发者能够更...
5. **并发设计模式**:书中的实例章节介绍了多种并发设计模式,如生产者消费者模型、读写锁策略、双检锁/双重校验锁(DCL)、线程局部变量等,这些模式为解决并发问题提供了模板化的解决方案。 6. **源码分析**:...
- **并发模式**:例如生产者-消费者模式、读写锁策略、双检锁等,熟悉这些模式有助于解决复杂的并发问题。 3. **Java虚拟机(JVM)**: - **内存管理**:了解堆、栈、方法区、本地方法栈以及垃圾回收机制,对于...
Java中双检锁/双重检查锁定(Double-Check Locking,DCL)和静态内部类是实现线程安全单例的常用方法。 5. 状态对象模式:用于在多线程中同步访问对象的状态,例如CountDownLatch、CyclicBarrier和Semaphore等并发...