7. Double-Checked Locking
双重检查锁定机制,是一个老生常谈的问题了。双重检查锁定机制已经被广泛的引用,特别是在多线程环境下的懒加载实现上。但是,如果没有额外的同步,它不能独立可靠的运行在Java平台。看这段代码:
public class Singleton {
private Singleton instance = null;
public Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
// other function
}
如果这段代码在多线程的环境下运行,它会导致很多的错误。最明显的就是,Singleton对象会被两次或两次以上的实例化。为了解决这个错误,我们只需要为getInstance()方法加上synchronized关键字,改写后的类如下:
public class Singleton {
private Singleton instance = null;
public synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
// other function
}
现在,每次调用getInstance()方法的时候,都会进行同步。双重检查锁机制就是为了避免instance不为null时产生额外的同步开销,所以有了一下代码:
public class Singleton {
private Singleton instance = null;
public Singleton getInstance(){
if(instance == null){
synchronized(this){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
// other function
}
可是,如果这段代码在经过优化的编译器或共享内存多处理器的存在情况下运行的话,是行不通的。主要是因为旧的Java内存模型导致,调用getInstance()方法后,看到非空instance 引用,但是其Singleton所有属性均为默认值,而非在构造函数中指定的值。如果编译器能够保证其构造函数不抛出异常或执行同步,编译器内联的调用了构造函数,那么对于初始化instance和其所有属性的写操作将重序。即使编译器不会重序,在一个多核处理或内存系统中也会进行重序。所以就有了如下的改善:
public class Singleton {
private Singleton instance = null;
public Singleton getInstance(){
if(instance == null){
Singleton s ;
synchronized(this){
s = instance;
if(s == null){
synchronized(this){
s = new Singleton();
} // release inner synchronization lock
}
instance = s;
}
}
return instance;
}
}
引用
This code puts construction of the Singleton object inside an inner synchronized block. The intuitive idea here is that there should be a memory barrier at the point where synchronization is released, and that should prevent the reordering of the initialization of the Helper object and the assignment to the field helper.
Unfortunately, that intuition is absolutely wrong. The rules for synchronization don't work that way. The rule for a monitorexit (i.e., releasing synchronization) is that actions before the monitorexit must be performed before the monitor is released. However, there is no rule which says that actions after the monitorexit may not be done before the monitor is released. It is perfectly reasonable and legal for the compiler to move the assignment instance = s; inside the synchronized block, in which case we are back where we were previously. Many processors offer instructions that perform this kind of one-way memory barrier. Changing the semantics to require releasing a lock to be a full memory barrier would have performance penalties.
Alexander Terekhov想出了一个非常巧妙的问题解决办法,通过ThreadLocal变量来实现双重检查锁机制问题。
public class Singleton {
/** If perThreadInstance.get() returns a non-null value, this thread
has done synchronization needed to see initialization
of helper */
private final ThreadLocal<Singleton> local = new ThreadLocal<Singleton>();
private Singleton instance = null;
public Singleton getInstance(){
if(local.get() == null){
createInstance();
}
return instance;
}
private final void createInstance(){
synchronized(this) {
if (instance == null)
instance = new Singleton();
}
local.set(instance);
}
// other function
}
这段代码的性能取决于JDK的实现。在JDK1.2,ThreadLocal非常的慢,1.3就有了明显的提升。虽然这段代码,在现在看来是错误的(因为现在新的Java内存模型),但是在当时,确实解决了双重检查锁机制导致的问题。
自JDK1.5后,新的Java内存模型和线程规范后,可以通过volitale关键字,防止读写重序,解决双重检查锁机制导致的问题:
public class Singleton {
private volatile Singleton instance = null;
public Singleton getInstance(){
if(instance == null){
synchronized(this){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
// other function
}
如果Singleton是不可变的对象,且其所有属性都是不可变的,那么就不需要volatile关键字修饰了。Java语言规定,因为对于一个不可变对象的引用,其行为和int,float是一样的,对其的读和写操作都是原子性的。
然而最简单高雅的方式创建instance莫过于使用static:
class Singleton {
private static Singleton instance = new Singleton();
}
Java语言规定,字段的初始化是从被引用后,而且所有的线程将从字段初始化后进行相应的操作。
Reference :
http://www.cs.umd.edu/~pugh/java/memoryModel/
http://gee.cs.oswego.edu/dl/cpj/jmm.html
分享到:
相关推荐
为了简化多线程编程,Java提供了一系列工具和API,如`java.util.Timer`和`java.util.concurrent`包,这些工具可以帮助开发者更高效地管理线程间的同步问题。 ##### 1.2 synchronized关键字 `synchronized`关键字是...
concurrent programming in java design principles and patterns .chm
Java的并发库提供了一系列工具和API,如`java.util.concurrent`包,帮助开发者有效地管理并发任务。本书主要涵盖以下几个方面: 1. **线程基础**:书中首先介绍了线程的基本概念,包括如何创建和管理线程,以及线程...
Java 1.5引入了`java.util.concurrent`包,包含了一系列的并发工具类,如线程池、阻塞队列、并发集合等。这些工具旨在提高并发性能并简化编程模型。例如,`ExecutorService`和`ThreadPoolExecutor`提供了线程池管理...
《Doug Lea, Concurrent Programming in Java Design Principles and Patterns》是一本深入探讨Java并发编程的经典著作,由Doug Lea撰写。这本书对于理解Java平台上的多线程编程和并发设计模式至关重要,是许多Java...
Java平台提供了丰富的API支持并发编程,如`java.util.concurrent`包下的各种类和接口,这些工具可以帮助开发者更高效地管理多线程环境下的任务调度和数据共享问题。 ### Java并发编程基础 #### 1. 多线程基础 - **...
《Java并发编程》一书是由著名并发编程专家Doug Lea所著,他同时也是Java并发包(JUC)的作者,这本书详细介绍了Java多线程编程的基础概念和高级技术。 首先,书中提到了并发编程的基本概念,包括并发模型、设计力...
本书《Concurrent Programming in Java™: Design Principles and Patterns 2nd》由Doug Lea编写,出版于1999年,是关于Java并发编程的一本权威指南。Java平台因其强大的线程支持能力而备受青睐,这使得Java程序员...
Concurrent Programming on Windows 英文无水印pdf pdf所有页面使用FoxitReader和PDF-XChangeViewer测试都可以打开 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系...
Title: Learning Concurrent Programming in Scala, 2nd Edition Author: Aleksandar Prokopec Length: 382 pages Edition: 2nd Revised edition Language: English Publisher: Packt Publishing - ebooks Account ...
Concurrent Programming in Java™: Design Principles and Patterns, Second Edition. 介绍并发编程的好的著作,著名的并发大师 Doug Lea的杰作。
《Java并发编程实践》第二版是一本专注于Java编程语言中并发程序设计的著作,由Doug Lea撰写,Addison Wesley出版社于1999年10月出版。这本书旨在为那些熟悉面向对象编程但对并发编程了解不多的开发者提供指导,同时...
例如,使用无锁数据结构或原子操作(`java.util.concurrent.atomic`包)。 3. **避免死锁、活锁和饥饿**:理解并预防这些并发问题至关重要。死锁发生在两个或多个线程相互等待对方释放资源导致僵局;活锁是线程不断...
Concurrent Programming in Java Design Principles and Pattern英文版 2.48M Java并发编程设计原则与模式_第二版(原书中文版) 19.4M Concurrent_Programming_in_Java_Design_Principles_Lecture DougLea
- **标准库**:Java并发工具包(java.util.concurrent)提供了丰富的并发工具类。 - **第三方库**:例如Apache Commons Concurrency等。 ##### 2. 构建库 - **自定义并发组件**:根据项目需求开发特定的并发工具。 ...
《Concepts and Notations for Concurrent Programming》这篇论文由Gregory R. Andrews和Fred B. Schneider撰写,深入探讨了并行编程的核心概念和技术。尽管这是一篇较为古老的文章,但其内容仍然具有很高的参考价值...
资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,...