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
分享到:
相关推荐
Concurrent Programming in Java Design Principles and Pattern英文版 2.48M Java并发编程设计原则与模式_第二版(原书中文版) 19.4M Concurrent_Programming_in_Java_Design_Principles_Lecture DougLea
详细的java 多线程相关知识 并附有相关练习题
Concurrent Programming in Java - Design Principles and Patterns
java concurrent 多线程 PPT
concurrent programming in java design principles and patterns .chm
Doug Lea, Concurrent Programming in Java Design Principles and Patterns
详细介绍java多线程编程的各个基础概念。JUC作者doug lea著
在并发或多线程应用程序中使用Java编程语言的简介。
word版本的资料,网上...Concurrent Programming in Java™: Design Principles and Patterns, Second Edition Doug Lea Publisher: Addison Wesley Second Edition October 01, 1999 ISBN: 0-201-31009-0, 432 pages
Doug Lea Java Concurrent Programming
This book shows readers how to use the Java platform's threading model more precisely by helping them to understand the patterns and tradeoffs associated with concurrent programming
Concurrent - Programming in Java.pdf,ppt,Doug Lea
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并发编程-设计原则与模式(Concurrent.Programming.in.Java-Design.Principles.and.Patterns(Second.Edition))(中英版)
Concurrent Programming in Java Thread 看看吧
资深Java专家10年经验总结,全程案例式讲解,首本全面介绍Java多线程编程技术的专著 结合大量实例,全面讲解Java多线程编程中的并发访问、线程间通信、锁等最难突破的核心技术与应用实践 封底 Java多线程无处不在,...