Java内存模型    在了解Java的同步秘密之前,先来看看JMM(Java Memory Model)。

Java被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。而且Java语言最大的特点就是废除了指针,把程序员从痛苦中解脱出来,不用再考虑内存使用和管理方面的问题。

可惜世事总不尽如人意,虽然JMM设计上方便了程序员,但是它增加了虚拟机的复杂程度,而且还导致某些编程技巧在Java语言中失效。

JMM主要是为了规定了线程和内存之间的一些关系。对Java程序员来说只需负责用synchronized同步关键字,其它诸如与线程/内存之间进行数据交换/同步等繁琐工作均由虚拟机负责完成。根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

线程若要对某变量进行操作,必须经过一系列步骤:首先从主存复制/刷新数据到工作内存,然后执行代码,进行引用/赋值操作,最后把变量内容写回Main Memory。Java语言规范(JLS)中对线程和主存互操作定义了6个行为,分别为load,save,read,write,assign和use,这些操作行为具有原子性,且相互依赖,有明确的调用先后顺序。具体的描述请参见JLS第17章。

我们在前面的章节介绍了synchronized的作用,现在,从JMM的角度来重新审视synchronized关键字。

假设某条线程执行一个synchronized代码段,其间对某变量进行操作,JVM会依次执行如下动作:

(1) 获取同步对象monitor (lock)

(2) 从主存复制变量到当前工作内存 (read and load)

(3) 执行代码,改变共享变量值 (use and assign)

(4) 用工作内存数据刷新主存相关内容 (store and write)

(5) 释放同步对象锁 (unlock)

可见,synchronized的另外一个作用是保证主存内容和线程的工作内存中的数据的一致性。如果没有使用synchronized关键字,JVM不保证第2步和第4步会严格按照上述次序立即执行。因为根据JLS中的规定,线程的工作内存和主存之间的数据交换是松耦合的,什么时候需要刷新工作内存或者更新主内存内容,可以由具体的虚拟机实现自行决定。如果多个线程同时执行一段未经synchronized保护的代码段,很有可能某条线程已经改动了变量的值,但是其他线程却无法看到这个改动,依然在旧的变量值上进行运算,最终导致不可预料的运算结果。

DCL失效   针对延迟加载法的同步实现所产生的性能低的问题,我们可以采用DCL,即双重检查加锁(Double Check Lock)的方法来避免每次调用getInstance()方法时都同步。

在开始讨论之前,先介绍一下LazyLoad,这种技巧很常用,就是指一个类包含某个成员变量,在类初始化的时候并不立即为该变量初始化一个实例,而是等到真正要使用到该变量的时候才初始化之。

例如下面的代码:class Foo {

private Resource res = null;

public Resource getResource() {

if (res == null)

res = new Resource();

return res;

}

}   由于LazyLoad可以有效的减少系统资源消耗,提高程序整体的性能,所以被广泛的使用,连Java的缺省类加载器也采用这种方法来加载Java类。

在单线程环境下,一切都相安无事,但如果把上面的代码放到多线程环境下运行,那么就可能会出现问题。假设有2条线程,同时执行到了if(res == null),那么很有可能res被初始化2次,为了避免这样的Race Condition,得用synchronized关键字把上面的方法同步起来。代码如下:

Class Foo {

private Resource res = null;

public synchronized Resource getResource() {

if (res == null)

res = new Resource();

return res;

}

}   synchronized过的方法在速度上要比未同步的方法慢上100倍,同时你也发现,只有第一次调用该方法的时候才需要同步,而一旦res初始化完成,同步完全没必要。所以你很快就把代码重构成了下面的样子:

Class Foo {

private Resource res = null;

private Date d = new Data();

public Resource getResource() {

if (res == null){ //(1)

synchronized(Foo.class){

if(res == null){

res = new Resource(); //(2)

}

}

}

return res;

}

}Double-Checked Locking看起来是非常完美的。但是很遗憾,根据Java的语言规范,上面的代码是不可靠的。

出现上述问题, 最重要的2个原因如下:

1, 编译器优化了程序指令, 以加快cpu处理速度.

2, 多核cpu动态调整指令顺序, 以加快并行运算能力.

问题出现的顺序:

1, 线程A, 发现对象未实例化, 准备开始实例化

2, 由于编译器优化了程序指令, 允许对象在构造函数未调用完前, 将共享变量的引用指向部分构造的对象, 虽然对象未完全实例化, 但已经不为null了.

3, 线程B, 发现部分构造的对象已不是null, 则直接返回了该对象.无法保证语句(2)和语句(1)不存在happen-before(详解跳转)关系.

一个线程A运行到"这里"时,A的工作区中,肯定已经产生一个Foo对象,而且这时这个对象已经完成了Data d.现在线程A调用时间到,执行权被切换到另一个线程B来执行,会有什么问题呢?如果res不为null,线程B获得了一个res,但可能res.getD()却还没有初始化.

对于"这里"这条语句,线程A还没有离开同步块.因为没有"离开同步块"这个条件,线程A的工作区没有强制与主存储器同步,这时工作区中有两个字段res,d。虽然在线程A的工作区res和d都是完整的,但有JSL没有强制不允许先把res映射到主存储区,如果哪个jvm实现按它的优化方案先把工作存储器中的res同步到主存储器了,这时正好线程B获取了,而d却没有同步过去,那么线程B就获取了res的引用却找不能res.getD()。

DCL解决方案1.DCL的替代 Initialize-On-Demand

Class ResSingleton {

public static Resource res = new Resource();

}

这里LazyFoo只有一个静态成员变量。当第一次使用ResSingleton.res的时候,JVM才会初始化一个Resource实例,并且JVM会保证初始化的结果及时写入主存,能让其他线程看到,这样就成功的实现了LazyLoad。

2、另外,可以将instance声明为volatile(详解跳转),即

private volatile static LazySingleton instance;

在读线程B读一个volatile变量后,写线程A在写这个volatile变量之前,所有可见的共享变量的值都将立即变得对读线程B可见。

参考:http://www.blogjava.net/weidagang2046/articles/3494.html

java dcl 失效解决_DCL失效原因和解决方案相关推荐

  1. 平安京服务器维护无法发布新内容,决战平安京无法登陆怎么解决 无法登陆原因及解决方案一览_3DM手游...

    在最近一段时间里,很多玩家在决战平安京中突然发现无法登陆游戏,为什么会无法登陆游戏?这个问题有办法解决吗?下面小编就为大家介绍决战平安京无法登陆的原因及解决办法,出现这种问题的小伙伴们快来看看吧. 决 ...

  2. 梦幻西游手游登录显示服务器错误,梦幻西游手游无法登陆怎么解决 无法登陆原因及解决方案一览_3DM手游...

    在最近一段时间里,很多玩家在梦幻西游中突然发现无法登陆游戏,为什么会无法登陆游戏?这个问题有办法解决吗?下面小编就为大家介绍梦幻西游无法登陆的原因及解决办法,出现这种问题的小伙伴们快来看看吧. 梦幻西 ...

  3. 天下手游服务器维修,天下手游无法登陆怎么解决 无法登陆原因及解决方案一览_3DM手游...

    在最近一段时间里,很多玩家在天下手游中突然发现无法登陆游戏,为什么会无法登陆游戏?这个问题有办法解决吗?下面小编就为大家介绍天下手游无法登陆的原因及解决办法,出现这种问题的小伙伴们快来看看吧. 天下手 ...

  4. 大话西游显示无法连接服务器,大话西游手游无法登陆怎么解决 无法登陆原因及解决方案一览_3DM手游...

    在最近一段时间里,很多玩家在大话西游中突然发现无法登陆游戏,为什么会无法登陆游戏?这个问题有办法解决吗?下面小编就为大家介绍大话西游无法登陆的原因及解决办法,出现这种问题的小伙伴们快来看看吧. 大话西 ...

  5. 率土之滨总是显示未登录服务器,率土之滨无法登陆怎么解决 无法登陆原因及解决方案一览_3DM手游...

    在最近一段时间里,很多玩家在率土之滨中突然发现无法登陆游戏,为什么会无法登陆游戏?这个问题有办法解决吗?下面小编就为大家介绍率土之滨无法登陆的原因及解决办法,出现这种问题的小伙伴们快来看看吧. 率土之 ...

  6. 楚留香手游无法获取服务器信息,楚留香手游无法登陆怎么解决 无法登陆原因及解决方案一览_3DM手游...

    作为一款跨时代的手游--楚留香手游,偶尔也会出现登陆不上的情况,那么,究竟为什么登录不上呢?下面就让3DM小编来为大家介绍一下楚留香手游无法登陆的原因吧! 楚留香手游无法登陆原因及解决方案一览 无法登 ...

  7. java字符串使用replace、replaceall、split处理`’+‘、’|‘、 ’*‘、’.‘、’?‘、'$'等字符无效的解决办法(阐释原因和解决方案,实测有效)

    文章目录 背景介绍 **java字符串使用replace.replaceall.split处理`'+'.'|'. '*'.'.'.'?'.'$'`等字符无效的2种异常表现方式:** 案例一.java使 ...

  8. Java内存泄露系列--内存泄露的原因及解决方案(大全)

    原文网址:Java内存泄露系列--内存泄露的原因及解决方案(大全)_IT利刃出鞘的博客-CSDN博客 简介 简介 本文介绍Java中内存泄露的一些原因与解决方案. 如果内存泄露的空间足够大,就会导致内 ...

  9. java金蝶星空云金蝶Java 对接 金蝶云星空 接口 对接 金蝶API 对接 金蝶 接口 解决 会话失效 问题 会话已失效,请重新登录

    java金蝶星空云金蝶Java 对接 金蝶云星空 接口 对接 金蝶API 对接 金蝶 接口 解决 会话失效 问题 会话已失效,请重新登录 1.准备工作 1.1 接口调用账户 1.2 下载 金蝶星空云 ...

最新文章

  1. FPGA中LUT、 LATCH 、FF
  2. Java中值传递和引用传递原理以及区别
  3. EZchip将推全球首款100核64位ARM A-53芯片
  4. 舍 bpftrace 而取 systemtap 的代价和思考
  5. 瀑布模型、V模型、原型模型、增量模型、螺旋模型、喷泉模型
  6. 计算机专业硕士求职经历(转)
  7. 流行前端几大UI框架排行榜
  8. 大学计算机网络实验网线制作,计算机网络实验报告 网线的制作.doc
  9. mac下webrtc的编译-坑记录 非常重要
  10. 第九届蓝桥杯,赛后感!!含泪写完。
  11. html想实现文字环绕图片,HTML/CSS实现文字环绕图片布局
  12. IE下载文件无法弹出下载框
  13. TP3.2中filed和find()使用
  14. Windows下遇到OSError: [WinError 6] 句柄无效的问题
  15. 白蛋白纳米粒|莫西沙星小鼠血清白蛋白MSA纳米粒|利多卡因大鼠血清白蛋白RSA纳米粒
  16. 【附源码】计算机毕业设计SSM宁夏旅游信息管理系统
  17. Revit建模快速剖面操作一键完成!
  18. Symantec Backup Exec 2010 安装报 bad ELF interpreter: No such file or directory
  19. ubuntu下的android JNI入门DEMO
  20. 网页js识别移动端几种方法

热门文章

  1. 基于CMake构建MSVC_CUDA及MinGW编译环境下的的OpenCV项目
  2. 肺功能曲线图怎么看_如何看肺功能结果报告单
  3. java简单毕设_计算机毕业设计之自定义毕设课题需要如何确定工作量
  4. Stream流思想和常用方法
  5. iOS appstore分级
  6. [java] 虚拟机(JVM)底层结构详解[转]
  7. 鸟哥学习笔记六(基础篇第十一章)
  8. Weblogic EJB 学习笔记(3)精
  9. 博文视点 OpenParty第11期:世界黑客大会那些事
  10. linux 安装nginx php mysql 配置文件在哪_linux下 php+nginx+mysql安装配置