博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java内存模型之从JMM角度分析DCL
阅读量:6895 次
发布时间:2019-06-27

本文共 2726 字,大约阅读时间需要 9 分钟。

hot3.png

Java内存模型之从JMM角度分析DCL 博客分类: java

DCL,即Double Check Lock,中卫双重检查锁定。其实DCL很多人在单例模式中用过,LZ面试人的时候也要他们写过,但是有很多人都会写错。他们为什么会写错呢?其错误根源在哪里?有什么解决方案?下面就随LZ一起来分析

问题分析

我们先看单例模式里面的懒汉式:

publicclassSingleton{
privatestaticSingleton singleton;privateSingleton(){}publicstaticSingleton getInstance(){
if(singleton ==null){
singleton =newSingleton();}return singleton;}}

我们都知道这种写法是错误的,因为它无法保证线程的安全性。优化如下:

publicclassSingleton{
privatestaticSingleton singleton;privateSingleton(){}publicstaticsynchronizedSingleton getInstance(){
if(singleton ==null){
singleton =newSingleton();}return singleton;}}

优化非常简单,就是在getInstance方法上面做了同步,但是synchronized就会导致这个方法比较低效,导致程序性能下降,那么怎么解决呢?聪明的人们想到了双重检查 DCL:

publicclassSingleton{
privatestaticSingleton singleton;privateSingleton(){}publicstaticSingleton getInstance(){
if(singleton ==null){
// 1synchronized(Singleton.class){
// 2if(singleton ==null){
// 3 singleton =newSingleton();// 4}}}return singleton;}}

就如上面所示,这个代码看起来很完美,理由如下:

  • 如果检查第一个singleton不为null,则不需要执行下面的加锁动作,极大提高了程序的性能;
  • 如果第一个singleton为null,即使有多个线程同一时间判断,但是由于synchronized的存在,只会有一个线程能够创建对象;
  • 当第一个获取锁的线程创建完成后singleton对象后,其他的在第二次判断singleton一定不会为null,则直接返回已经创建好的singleton对象;

通过上面的分析,DCL看起确实是非常完美,但是可以明确地告诉你,这个错误的。上面的逻辑确实是没有问题,分析也对,但是就是有问题,那么问题出在哪里呢?在回答这个问题之前,我们先来复习一下创建对象过程,实例化一个对象要分为三个步骤:

  1. 分配内存空间
  2. 初始化对象
  3. 将内存空间的地址赋值给对应的引用

但是由于重排序的缘故,步骤2、3可能会发生重排序,其过程如下:

  1. 分配内存空间
  2. 将内存空间的地址赋值给对应的引用
  3. 初始化对象

如果2、3发生了重排序就会导致第二个判断会出错,singleton != null,但是它其实仅仅只是一个地址而已,此时对象还没有被初始化,所以return的singleton对象是一个没有被初始化的对象,如下:

按照上面图例所示,线程B访问的是一个没有被初始化的singleton对象。

通过上面的阐述,我们可以判断DCL的错误根源在于步骤4:

singleton =newSingleton();

知道问题根源所在,那么怎么解决呢?有两个解决办法:

  1. 不允许初始化阶段步骤2 、3发生重排序。
  2. 允许初始化阶段步骤2 、3发生重排序,但是不允许其他线程“看到”这个重排序。

解决方案

解决方案依据上面两个解决办法即可。

基于volatile解决方案

对于上面的DCL其实只需要做一点点修改即可:将变量singleton生命为volatile即可:

publicclassSingleton{
//通过volatile关键字来确保安全privatevolatilestaticSingleton singleton;privateSingleton(){}publicstaticSingleton getInstance(){
if(singleton ==null){
synchronized(Singleton.class){
if(singleton ==null){
singleton =newSingleton();}}}return singleton;}}

当singleton声明为volatile后,步骤2、步骤3就不会被重排序了,也就可以解决上面那问题了。

基于类初始化的解决方案

该解决方案的根本就在于:利用classloder的机制来保证初始化instance时只有一个线程。JVM在类初始化阶段会获取一个锁,这个锁可以同步多个线程对同一个类的初始化

publicclassSingleton{
privatestaticclassSingletonHolder{
publicstaticSingleton singleton =newSingleton();}publicstaticSingleton getInstance(){
returnSingletonHolder.singleton;}}

这种解决方案的实质是:运行步骤2和步骤3重排序,但是不允许其他线程看见。

Java语言规定,对于每一个类或者接口C,都有一个唯一的初始化锁LC与之相对应。从C到LC的映射,由JVM的具体实现去自由实现。JVM在类初始化阶段期间会获取这个初始化锁,并且每一个线程至少获取一次锁来确保这个类已经被初始化过了。

 

 

 

http://cmsblogs.com/?p=2161

转载于:https://my.oschina.net/xiaominmin/blog/1598453

你可能感兴趣的文章
Algeco Scotsman将召开2016年第三季度业绩电话会议
查看>>
新加坡IMDA计划进行Li-Fi测试
查看>>
《深入理解大数据:大数据处理与编程实践》一一1.3 MapReduce并行计算技术简介...
查看>>
LoadRunner关联的高级应用
查看>>
如何减少返工工作量?
查看>>
《敏捷可执行需求说明 Scrum提炼及实现技术》—— 2.1 界定不可更改的边界
查看>>
关注安防行业 聚焦公共安防系统
查看>>
Android代码(Handler的运用),HttpURLConnection的应用,将url图片地址转换成图片。...
查看>>
MySQL锁系列(七)之 锁算法详解
查看>>
webOS 更名 LuneOS,新版本名为 Affogato
查看>>
《UNIX环境高级编程(第3版)》——导读
查看>>
11_Eclipse中演示Git版本的创建,历史版本的修改,创建分支,合并历史版本和当前版本...
查看>>
《实施Cisco统一通信管理器(CIPT1)》一1.2 CUCM概述
查看>>
《容器技术系列》一1.1 引言
查看>>
编程语言:变革创业思维的工具
查看>>
第一个libgdx程序--仿别踩白块
查看>>
一个开源项目维护者的笔记 — 为什么我关闭 PRs
查看>>
技术人员要失业?未来80% IT 工作将自动化
查看>>
Apache Spark机器学习.1.4 MLlib
查看>>
腾讯Android自动化测试实战3.1.1 什么是Robotium
查看>>