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 ...
随机推荐
- vue动态路由
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件.例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染.能够提供参数的路由即为动态路由第一步:定义组件 c ...
- Keras预测股票
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Sat Nov 18 21:22:29 201 ...
- PuTTY乱码问题解决办法
原文链接:http://www.henshiyong.com/archives/403.html 使用PuTTY 时,遇到了乱码问题,查看了别人介绍的信息,解决掉了. 方法其实很简单,现在分享出来. ...
- TreeSet集合为什么要实现Comparable?
首先,让我们来看看JDK中TreeSet类的add方法 /** * Adds the specified element to this set if it is not already presen ...
- 配置weblogic nodemanager管理器
[内容提示]:看了一场皇族对阵OMG的比赛,失落 .皇族又输了.. 可爱的UZI... 我承认我是逗比,看了3天节点管理器才明白点东西 .笨笨 .. 废话不多说,上图!所有的故意围绕一张图开始了 ...
- BUG 图片元素img下 高度超出 出现多余空白
BUG 图片元素img下 高度超出 出现多余空白 1.将图片转换为块级对象 即,设置img为“display:block;”. 2.设置图片的垂直对齐方式 即设置图片的vertical-align ...
- 最基本的CentOS 网络配置
一般CentOS 网络配置是根据自己的需求来设定的.但是,对于一些不经常用CentOS的用户来说,不知道基本的CentOS 网络配置.如果你没有特别的要考虑的设置,那么就可以考虑下我推荐的这种Cent ...
- noip第17课作业
1. 召见骑士 [问题描述] 某王国有5位骑士,每位骑士都有自己的编号,且这个王国的编号都为奇数,分别为1,3,5,7,9,在国王召见他们之前他们都必须经过只能从一边进出的长廊,长廊的宽度只能坐一个 ...
- noip第16课作业
1. 猴子吃桃 [问题描述] 猴子第一天摘了若干个桃子,当即吃了一半,还不解馋,又多吃了一个:第二天,吃剩下的桃子的一半,还不过瘾,又多吃了一个:以后每天都吃前一天剩下的一半多一个,到第10天想再吃时 ...
- 10.Date对象
Date()对象 Date对象用于处理日期和时间. Math对象 ◆Math.ceil() 天花板函数 向上取整 ★如果是整数,取整之后是这个数本身 ★如果是小数,对数进行向上舍入. ◆Ma ...