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 - 双重检查锁定模式
问题描述 现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能:在实际开发过程中,会专门有一个日志模块,负责写日志,由于在系统的任何地方,我们都有可能要调用日志模块中的函数,进 ...
随机推荐
- 微信小程序开发教程(八)视图层——.wxml详解
框架的视图层由WXMKL(WeiXin Markup language)与WXSS(WeiXin Style Sheet)编写,由组件进行展示. 对于微信小程序而言,视图层就是所有.wxml文件与.w ...
- Codechef A Game With a Sheet of Paper
Discription Yuuko and Nagi like to play the following game: Initially they take a checkered sheet of ...
- [xsy2369]取名字
真是道挺好的题,做一道题学了挺多东西 从操作入手比较困难,所以对硬币进行讨论 考虑一个硬币$(A,B)$,假设$A\lt B$,那么我们可以把操作分成三类 第一类$T_j\lt A$,这种操作是没用的 ...
- 【AC自动机】【矩阵乘法】poj2778 DNA Sequence
http://blog.csdn.net/morgan_xww/article/details/7834801 讲得很好~可以理解自动机的本质,就是一个用来状态转移的东西~对于确定的输入而言,可以从初 ...
- Problem V: 零起点学算法20——输出特殊值II
#include<stdio.h> int main() { printf("\\n"); ; }
- 玩转JavaScript正则表达式
Why Regular Expression 我们先来看看,我们干哈要学正则表达式这玩意儿: 复杂的字符串搜寻.替换工作,无法用简单的方式(类似借助标准库函数)达成. 能够帮助你进行各种字符串验证. ...
- MORMOT数据库连接池
MORMOT数据库连接池 MORMOT封装了一堆的PROPS控件,用于连接各种数据库. MORMOT的封装是武装到了牙齿的,这堆PROPS控件居然数据库连接池也封装好了.这就为我们省了不少事,笔者非常 ...
- 【java】JDK安装后,没有配置环境变量,也可以java -version查看到版本信息
JDK安装后,没有配置环境变量,也可以java -version查看到版本信息 原因是:jdk安装过程,java.javaw.javaws三个命令被复制到C:\windows\system32目录下 ...
- PHP测试用例-前言 1
前提知识准备 在学习本课程之前,你需要准备以下知识点: 掌握一般的PHP开发技能,使用面向对象的框架开发过三个月以上 会一些JS知识 了解http协议 拥有以下知识会学得更加顺利: 掌握PHPUnit ...
- 使用Faric+Git进行分布式代码管理
Fabric是一个Python库,可以通过SSH在多个host上批量执行任务. 可以通过编写任务脚本,然后通过Fabric在本地就可以使用SSH在大量远程服务器上自动运行. 这些功能非常适合应用的自动 ...