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 ...
随机推荐
- leaflet入门(五)API翻译(下)
L.PointConstructor(函数构造器)Properties(属性)Methods(方法) L.BoundsConstructor(函数构造器)Properties(属性)Methods(方 ...
- python advanced programming (Ⅲ)
IO编程 IO在计算机中指Input/Output.由于程序和运行时数据是在内存中驻留,由CPU来执行,涉及到数据交换的地方,通常是磁盘.网络等,就需要IO接口. IO编程中,Stream(流)是一个 ...
- bzoj2388(分块 凸包)
好像没有什么高级数据结构能够很高效地实现这个东西: 那就上万能的分块,我们用一些数形结合的思想,把下标看成横坐标,前缀和的值看成纵坐标: 给区间内每个数都加k相当于相邻两点的斜率都加上k: 这种东西我 ...
- day36(动态代理)
动态代理 动态代理:是实现增强类中的一种方式,jdk中的动态代理:Proxy对象,使用最广泛的是在AOP切面编程中. 实现一个简单的动态代理来了解其中的运行机制. 创建一个接口:Person类型的接口 ...
- c语言:简单排序:冒泡排序法、选择排序法、插入排序法(待写)
1.冒泡排序法: 假设有n个数需要按从小到大排序,冒泡排序的原理是,在这一排数字中,将第一个数与第二个数比较大小,如果后面的比前面的小,就将他们交换位置.然后再比较第二个和第三个,再交换,直到第n-1 ...
- Beta阶段第四篇Scrum冲刺博客-Day3
1.站立式会议 提供当天站立式会议照片一张 2.每个人的工作 (有work item 的ID),并将其记录在码云项目管理中: 昨天已完成的工作. 张晨晨:学习新的测试模块需要的东西 郭琪容:学习复习模 ...
- MessageFormat.format()和String.format()
MessageFormat 提供了以与语言无关方式生成连接消息的方式.使用此方法构造向终端用户显示的消息. MessageFormat 获取一组对象,格式化这些对象,然后将格式化后的字符串插入到模式中 ...
- sublime text配置node.js调试
1. 首先到 nodejs.org 下载 Node.js 安装包并安装.2. 打开 Sublime Text 2 编辑器.选择菜单 Tools --> Build System --> n ...
- POJ3020 Antenna Placement
Antenna Placement Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 9586 Accepted: 4736 ...
- 转:mysql触发器
原文地址:http://www.cnblogs.com/nicholas_f/archive/2009/09/22/1572050.html CREATE TRIGGER <触发器名称> ...