JAVA 双重检查锁定和延迟初始化
双重检查锁定的由来
在Java程序中,有时需要推迟一些高开销的对象的初始化操作,并且只有在真正使用到这个对象的时候,才进行初始化,此时,就需要延迟初始化技术。
延迟初始化的正确实现是需要一些技巧的,否则容易出现问题,下面一一介绍。
方案1
public class UnsafeLazyInit{
private static Instance instance;
public static Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
}
这种做法的错误是很明显的,如果两个线程分别调用getInstance,由于对共享变量的访问没有做同步,很容易出现下面两种情况:
1.线程A和B都看到instance没有初始化,于是分别进行了初始化。
2.instance=new Instance操作被重排序,实际执行过程可能是:先分配内存,然后赋值给instance,最后再执行初始化。
如果是这样的话,其他线程可能就会读取到尚未初始化完成的instance对象。
方案2
public class UnsafeLazyInit{
private static Instance instance;
public static synchronized Instance getInstance(){
if (instance == null){
instance = new Instance();
}
return instance;
}
}
这种做法的问题是很明显的,每一次读取instance都需要同步,可能会对性能产生较大的影响。
方案3
方案3是一个错误的双重检测加锁实现,看代码:
public class UnsafeLazyInit{
private static Instance instance;
public static Instance getInstance(){
if (instance == null){
synchronized(UnsafeLazyInit.classs){
if (instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
这种方案看似解决了上面两种方案都存在的问题,但是也是有问题的。
问题根源
instance = new Instance();
这一条语句在实际执行中,可能会被拆分程三条语句,如下:
memory = allocate();
ctorInstance(memory); //2
instance = memory; //3
根据重排序规则,后两条语句不存在数据依赖,因此是可以进行重排序的。
重排序之后,就意味着,instance域在被赋值了之后,指向的对象可能尚未初始化完成,而instance域是一个静态域,
可以被其他线程读取到,那么其他线程就可以读取到尚未初始化完成的instance域。
基于volatile的解决方案
要解决这个办法,只需要禁止语句2和语句3进行重排序即可,因此可以使用volatile来修改instance就能做到了。
private volatile static Instance instance;
因为Volatile语义会禁止编译器将volatile写之前的操作重排序到volatile之后。
基于类初始化的解决方案
Java语言规范规定,对于每一个类或者接口C ,都有一个唯一的初始化锁LC与之对应,从C到LC的映射,由JVM实现。
每个线程在读取一个类的信息时,如果此类尚未初始化,则尝试获取LC去初始化,如果获取失败则等待其他线程释放LC。
如果能获取到LC,则要判断类的初始化状态,如果是位初始化,则要进行初始化。如果是正在初始化,
则要等待其他线程初始化完成,如果是已经初始化,则直接使用此类对象。
public class InstanceFactory {
private static class InstanceHolder {
public static Instance instance = new Instance();
}
public static Instance getInstance() {
return InstanceHolder.instance ; //这里将导致InstanceHolder类被初始化
}
}
结论
字段延迟初始化降低了初始化类或者创建实例的开销,但是增加零访问被延迟促使化的字段的开销。
在大部分时候,正常的初始化要优于延迟初始化。如果确实需要对实例字段使用线程安全的延迟初始化,
请使用上面介绍的基于volatile的延迟初始化方案;如果确实需要对静态字段使用线程安全的延迟初始化,
请使用上面基于类初始化方案的延迟初始化。
JAVA 双重检查锁定和延迟初始化的更多相关文章
- volatile双重检查锁定与延迟初始化
一.基本概念: 1.volatile是轻量级的synchronized,在多核处理器开发中保证了共享变量的“可见性”.可见性的意思是,当一个线程修改一个共享变量时,另一个线程能读到这个修改的值. 2. ...
- 双重检查锁定与延迟初始化(转自infoq)
很好的文章,转自http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization 在java程序中,有 ...
- 从学习“单例模式”学到的Java知识:双重检查锁和延迟初始化
一切真是有缘,上午刚刚看完单例模式,还在为其中的代码块同步而兴奋,下午就遇见这篇文章:双重检查锁定与延迟初始化.我一看,文章开头语出惊人,说这是一种错误的优化,我说,难道上午学的东西下午就过时了吗?仔 ...
- Java盲点:双重检查锁定及单例模式
尊重原创: http://gstarwd.iteye.com/blog/692937 2004 年 5 月 01 日 所有的编程语言都有一些共用的习语.了解和使用一些习语很有用,程序员们花费宝贵的时间 ...
- java 双重检查模式
java 双重检查模式 在并发环境下 兼顾安全和效率 成例(Idiom)是一种代码层次上的模式,是在比设计模式的层次更具体的层次上的代码技巧.成例往往与编程语言密切相关.双重检查成例(Double C ...
- DCL,即Double Check Lock,中卫双重检查锁定。
DCL,即Double Check Lock,中卫双重检查锁定. [Java并发编程]之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码) 关于单例.关于DCL: ...
- 利用双重检查锁定和CAS算法:解决并发下数据库的一致性问题
背景 最近有一个场景遇到了数据库的并发问题.现在先由我来抽象一下,去掉不必要的繁杂业务. 数据库表book存储着每本书的阅读量,一开始数据库是空的,不存在任何的数据.当用户访问接口的时候,判断 ...
- Singleton(单例)模式和Double-Checked Locking(双重检查锁定)模式
问题描述 现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能:在实际开发过程中,会专门有一个日志模块,负责写日志,由于在系统的任何地方,我们都有可能要调用日志模块中的函数,进 ...
- Singleton - 单例模式和Double-Checked Locking - 双重检查锁定模式
问题描述 现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能:在实际开发过程中,会专门有一个日志模块,负责写日志,由于在系统的任何地方,我们都有可能要调用日志模块中的函数,进 ...
随机推荐
- [ARC087D]FT Robot
题目大意: 一个机器人按照给定的一系列指令进行运动. 总共有两种指令: T:向某个方向旋转90度. F:向当前所朝的方向走一个单位长度. 一开始机器人站在原点,且朝向x的正半轴方向,问机器人是否可能会 ...
- map写数据到本地磁盘过程解析----spill和merge
如上次分析,其实map函数中的context.write()调用过程如下所示: 梳理下调用过程,context的write方法其实是调用了TaskInputOutputContext类的write方法 ...
- 自定义PHP页面跳转函数redirect($url, $time = 0, $msg = '')
利用PHP的header()函数,可以实现页面跳转,如 header("Location: " . $url); 但它有个缺点,一旦HTTP报头块已经发送,就不能使用 header ...
- 设置cookie和查找cookie的方法
1.设置cookie(名称,值,过期时间) function setCookie(key,value,d){ if(d === undefined){ document.cookie = encode ...
- javascript函数,构造函数。js对象和json的区别。js中this指向问题
函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块.好处:在出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句.(1)语法:函数就是包裹在花括号中的代码 ...
- 利用CMD查看系统硬件信息
利用CMD查看系统硬件信息对于在windows下查看系统信息大家一定不陌生了,我现在说几个最常用的方法,对命令感兴趣的朋友看看,(给菜鸟看的,老手就不要笑话我了,大家都是从那个时候过来的,^_^).一 ...
- http://jingyan.baidu.com/article/dca1fa6fa07000f1a44052f6.html
http://jingyan.baidu.com/article/dca1fa6fa07000f1a44052f6.html
- 算法导论-求x的n次方
目录 1.分治求x的n次方思路 2.c++代码实现 内容 1.分治求x的n次方思路T(n)=Θ(lgn) 为了计算乘方数a^n,传统的做法(所谓的Naive algorithm)就是循环相乘n次,算法 ...
- Netty4 initAndRegister 解析
我们从框架的应用层面来分析,NioEventLoopGroup在netty中的使用. 这是我们需要配置的地方. 紧接着我们进入netty的运行中.ServerBootstrap.bind(PORT); ...
- Centos中mount命令挂载windows7共享文件夹
1) 在ip:10.4.35.77的windows机器上新建用户.这里新建username:myshare,password:myshare123. 选择 [计算机]右键 选择[管理],本地用户和组 ...