volatile双重检查锁定与延迟初始化
一、基本概念:
1、volatile是轻量级的synchronized,在多核处理器开发中保证了共享变量的“可见性”。可见性的意思是,当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。
2、volatile在修饰共享变量进行写操作时,在多核处理器下会引发两件事情:
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
3、在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议。每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,
当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态。当处理器对这个数据进行修改操作的时候,会重新从系统内存
中把数据读到处理器缓存里。
4、锁的happens-before规则保证释放锁和获取锁两个线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量的最后
写入。即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读/写就有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,
这些操作整体上不具有原子性。简而言之:
1)可见性。对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量的最后写入。
2)原子性。对任意一个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
5、volatile写-读内存语意:
写语意:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
读语意:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量。
二、volatile在双重检查锁定与延迟初始化中的应用
在Java多线程程序中,有时候需要采用延迟初始化来降低初始化类和创建对象的开销。双重检查锁定是常见的初始化技术。先看个双重检查锁定和延迟初始化的例子。
public class Singleton {
private Singleton(){}
private static Singleton singleton = null;
public static Singleton getSafe2Instance() {
if(singleton == null) { //①第一次检查
synchronized (Singleton.class) { //②加锁
if(singleton == null) { //③第二次检查
singleton = new Singleton(); //④问题出现的地方
}
}
}
return singleton;
}
}
上边的代码有个问题,当线程执行到①处,发现singleton不为空,但是singleton引用的对象有可能还没有完成初始化。那线程获取到的singleton的引用就有可能是空的,
导致程序出错。为什么会出现线程获取到的singleton的引用时空的呢?我们看一下问题的根源。线程执行到④处,创建了一个对象,这一行代码可以分解为如下三行伪代码。
memory = allocate(); //1:分配对象内存空间
ctorInstance(memory); //2:初始化对象
singleton = memroy; //3:设置singleton指向刚分配的内存地址
第2行和第3行伪代码在编译器里可能会重排序。重排序后的伪代码为:
memory = allocate(); //1:分配对象内存空间
singleton = memroy; //3:设置singleton指向刚分配的内存地址
ctorInstance(memory); //2:初始化对象
如果发生重排序,另一个并发执行的线程B就有可能在①处判断instance不为null,线程B接下来将访问instance锁引用的对象,但此时这个对象可能还没有被线程A初始化。
那怎么解决这个问题呢?
1)不允许2和3重排序
2)允许2和3重排序,但不允许其他线程“看到”这个重排序。
针对第一条我们可以用volatile来解决,因为volatile有防止重排序的能力。
public class Singleton {
private Singleton(){}
private volatile static Singleton singleton = null;
public static Singleton getSafe2Instance() {
if(singleton == null) {
synchronized (Singleton.class) {
if(singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
针对第二条,我们可以记录类初始化的解决方案。因为JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,
JVM会去获取一个锁。这个锁可以同步多个线程对同一个类的初始化。基于这个特性可以实现另一种线程安全的延迟初始化方案(这个方案被称之为Initialization On Demand Holder idion)
public class Singleton {
private Singleton(){}
private volatile static Singleton singleton = null;
public static final Singleton getSafe3Instance() {
return LazyHolder.INSTANCE;
}
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
参考:
[1]《Java并发编程艺术》,方腾飞
{2}《Java高并发程序设计》,葛一鸣
volatile双重检查锁定与延迟初始化的更多相关文章
- 双重检查锁定与延迟初始化(转自infoq)
很好的文章,转自http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization 在java程序中,有 ...
- JAVA 双重检查锁定和延迟初始化
双重检查锁定的由来在Java程序中,有时需要推迟一些高开销的对象的初始化操作,并且只有在真正使用到这个对象的时候,才进行初始化,此时,就需要延迟初始化技术.延迟初始化的正确实现是需要一些技巧的,否则容 ...
- 从学习“单例模式”学到的Java知识:双重检查锁和延迟初始化
一切真是有缘,上午刚刚看完单例模式,还在为其中的代码块同步而兴奋,下午就遇见这篇文章:双重检查锁定与延迟初始化.我一看,文章开头语出惊人,说这是一种错误的优化,我说,难道上午学的东西下午就过时了吗?仔 ...
- Java盲点:双重检查锁定及单例模式
尊重原创: http://gstarwd.iteye.com/blog/692937 2004 年 5 月 01 日 所有的编程语言都有一些共用的习语.了解和使用一些习语很有用,程序员们花费宝贵的时间 ...
- DCL,即Double Check Lock,中卫双重检查锁定。
DCL,即Double Check Lock,中卫双重检查锁定. [Java并发编程]之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码) 关于单例.关于DCL: ...
- 利用双重检查锁定和CAS算法:解决并发下数据库的一致性问题
背景 最近有一个场景遇到了数据库的并发问题.现在先由我来抽象一下,去掉不必要的繁杂业务. 数据库表book存储着每本书的阅读量,一开始数据库是空的,不存在任何的数据.当用户访问接口的时候,判断 ...
- Singleton(单例)模式和Double-Checked Locking(双重检查锁定)模式
问题描述 现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能:在实际开发过程中,会专门有一个日志模块,负责写日志,由于在系统的任何地方,我们都有可能要调用日志模块中的函数,进 ...
- Singleton - 单例模式和Double-Checked Locking - 双重检查锁定模式
问题描述 现在,不管开发一个多大的系统(至少我现在的部门是这样的),都会带一个日志功能:在实际开发过程中,会专门有一个日志模块,负责写日志,由于在系统的任何地方,我们都有可能要调用日志模块中的函数,进 ...
- 单例模式中用volatile和synchronized来满足双重检查锁机制
背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的. 例子1 ...
随机推荐
- CURL模拟表单post提交及相关常用参数的使用(包括提交表单同时上传文件)
转载自:https://blog.csdn.net/freedomwjx/article/details/43278157 (注:在curl前面加上time如time curl xxx,可以在最后显示 ...
- Atcoder Regular-074 Writeup
C - Chocolate Bar 题面 There is a bar of chocolate with a height of H blocks and a width of W blocks. ...
- 树形控件(CTreeCtrl和CTreeView)
如何插入数据项目? 如何添加鼠标右击事件? 插入数据项 通过InsertItem()方法,有四种重载样式: HTREEITEM InsertItem(LPTVINSERTSTRUCT lpInsert ...
- 重建控制文件ORA-12720
重建过程出错:ORA-01503: CREATE CONTROLFILE failedORA-12720: operation requires database is in EXCLUSIVE mo ...
- codehouse
1 # 整数部分十进制转二进制 2 3 num = int(raw_input(">>>")) 4 5 if num < 0: 6 isNeg = True ...
- iOS程序的执行顺序 和 UIViewController的生命周期
iOS程序的执行顺序 1 进入程序的入口 进入main函数, 设置AppDelegate称为函数的代理 2 程序完成加载 -[AppDelegate application:didFinishLau ...
- Java输入输出技术
输入输出分类 输入流,相对计算机来说是输入的,例如鼠标键盘操作,设备给计算机的信息 输出流,相对计算机来说是输出的,例如屏幕显示,计算机给设备的信息. 具体分类 基本流,I ...
- (最长公共子序列 暴力) Common Subsequence (poj 1458)
http://poj.org/problem?id=1458 Description A subsequence of a given sequence is the given sequence w ...
- index.jsp首页访问不了的解决方法
解决方法: Tomcat,将项目添加到里面 部署解包的webapp目录 将Web项目部署到Tomcat中的方法之一,是部署没有封装到WAR文件中的Web项目.要使用这一方法部署未打包的webapp目录 ...
- Hdu1205 吃糖果 2017-06-29 14:26 24人阅读 评论(0) 收藏
吃糖果 Problem Description HOHO,终于从Speakless手上赢走了所有的糖果,是Gardon吃糖果时有个特殊的癖好,就是不喜欢将一样的糖果放在一起吃,喜欢先吃一种,下一次吃另 ...