热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java单例dcl_浅谈java单例模式DCL的缺陷及单例的正确写法

1前言单例模式是我们经常使用的一种模式,一般来说很多资料都建议我们写成如下的模式:***Createdbyqiyei2015on2017513.*pub

1 前言

单例模式是我们经常使用的一种模式,一般来说很多资料都建议我们写成如下的模式:

/**

* Created by qiyei2015 on 2017/5/13.

*/

public class Instance {

private String str = "";

private int a = 0;

private static Instance ins = null;

/**

* 构造方法私有化

*/

private Instance(){

str = "hello";

a = 20;

}

/**

* DCL方式获取单例

* @return

*/

public static Instance getInstance(){

if (ins == null){

synchronized (Instance.class){

if (ins == null){

ins = new Instance();

}

}

}

return ins;

}

}

但是这种方式其实是有缺陷的,具体什么缺陷呢?我们首先要了解JVM了内存模型,请看下面分析

2 JVM内存模型

JVM模型如下图:

b0495e01c35d2309b5879bacf156f3bb.png

这里着重介绍下VM Stack,其他的我相信都比较熟悉。

VM Stack是线程私有的区域。他是java方法执行时的字典:它里面记录了局部变量表、 操作数栈、 动态链接、 方法出口等信息。

在《java虚拟机规范》一书中对这部分的描述如下:

栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。

栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

栈帧的存储空间分配在 Java 虚拟机栈( §2.5.5)之中,每一个栈帧都有自己的局部变量表( Local Variables, §2.6.1)、操作数栈( OperandStack, §2.6.2)和指向当前方法所属的类的运行时常量池( §2.5.5)的引用。

java中某个线程在访问堆中的线程共享变量时,为了加快访问速度,提升效率,会把该变量临时拷贝一份到自己的VM Stack中,并保持和堆中数据的同步。

3 传统DCL方式的缺陷

有了以上的基础知识我们就可以知道DCL方式的缺陷在哪儿了。当线程A在获取了Instance.class锁时,对ins进行 ins = new Instance() 初始化时,由于这是很多条指令,jvm可能会乱序执行。

这个时候如果线程B在执行if (ins == null)时,正常情况下,如果为true,说明需要获取Instance.class锁,等待初始化。

但是这时候,假设线程A再没有对ins进行初始化完,比如只对str进行了赋值,还没有来的及对a进行赋值,假如jvm将未完成赋值的值拷贝回堆中,这个时候线程B有可能读到的值就不是为null了,就会造成数据丢失的情况。这时候我们发现线程B获取的对象中a的值是0,而不是20

因为:对ins的写操作不 happen-before 对它的读操作

这就是DCL方式的缺陷,那么怎么避免呢?首先我们需要了解分析多线程的一大利器

4 happen-before原则

Happen-Before规则:

1 同一个线程中,书写在前面的操作happen-before书写在后面的操作。这条规则是说,在单线程 中操作间happen-before关系完全是由源代码的顺序决定的,这里的前提“在同一个线程中”是很重要的,这条规则也称为单线程规则 。

这个规则多少说得有些简单了,考虑到控制结构和循环结构,书写在后面的操作可能happen-before书写在前面的操作,不过我想读者应该明白我的意思。

2 对锁的unlock操作happen-before后续的对同一个锁的lock操作。这里的“后续”指的是时间上的先后关系,unlock操作发生在退出同步块之后,lock操作发生在进入同步块之前。这是条最关键性的规则,线程安全性主要依赖于这条规则。

但是仅仅是这条规则仍然不起任何作用,它必须和下面这条规则联合起来使用才显得意义重大。这里关键条件是必须对“同一个锁”的lock和unlock。

如果操作A happen-before操作B,操作B happen-before操作C,那么操作A happen-before操作C。这条规则也称为传递规

3 对volatile字段的写操作happen-before后续的对同一个字段的读操作.(Java5 新增)

4 单例模式的正确写法

有了以上的分析我们知道,我们只需要在保证对ins的访问是读在写之后即可,因此正确的做法是在ins 前加上一个关键字volatile。因此DCL的正确写法应该如下:

/**

* Created by qiyei2015 on 2017/5/13.

*/

public class Instance {

private String str = "";

private int a = 0;

private volatile static Instance ins = null;

/**

* 构造方法私有化

*/

private Instance(){

str = "hello";

a = 20;

}

/**

* DCL方式获取单例

* @return

*/

public static Instance getInstance(){

if (ins == null){

synchronized (Instance.class){

if (ins == null){

ins = new Instance();

}

}

}

return ins;

}

}

其实单例模式也有另一种我很喜欢的写法,那就是内部类:

/**

* Created by qiyei2015 on 2017/5/13.

*/

public class Instance {

/**

* 构造方法私有化

*/

private Instance(){

}

private static class SingleHolder{

private static final Instance ins = new Instance();

}

/**

* 内部类方式获取单例

* @return

*/

public static Instance getInstance(){

return SingleHolder.ins;

}

}

这种从jvm虚拟机上保证了单例,并且也是懒式加载。

以上这篇浅谈java 单例模式DCL的缺陷及单例的正确写法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。



推荐阅读
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了Java中Currency类的getInstance()方法,该方法用于检索给定货币代码的该货币的实例。文章详细解释了方法的语法、参数、返回值和异常,并提供了一个示例程序来说明该方法的工作原理。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • Netty源代码分析服务器端启动ServerBootstrap初始化
    本文主要分析了Netty源代码中服务器端启动的过程,包括ServerBootstrap的初始化和相关参数的设置。通过分析NioEventLoopGroup、NioServerSocketChannel、ChannelOption.SO_BACKLOG等关键组件和选项的作用,深入理解Netty服务器端的启动过程。同时,还介绍了LoggingHandler的作用和使用方法,帮助读者更好地理解Netty源代码。 ... [详细]
author-avatar
U友50140932
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有