DCL单例模式中的缺陷及单例模式的其他实现
DCL:Double Check Lock ,意为双重检查锁。在单例模式中懒汉式中可以使用DCL来保证程序执行的效率。
 1 public class SingletonDemo {
 2     private static SingletonDemo singletonDemo = null;
 3     private SingletonDemo(){
 4     }
 5
 6     public SingletonDemo getSingletonDemo(){
 7         if(singletonDemo == null){
 8             synchronized (SingletonDemo.class){
 9                 if(singletonDemo == null){
10                     singletonDemo = new SingletonDemo();
11                 }
12             }
13         }
14         return singletonDemo;
15     }
16 }
上面是传统的DCL单例模式一种实现,第一个非空判断是为了避免实例属性已经实例化赋值后,后面的线程依然进入 synchronized 修饰的代码块,进行加锁、解锁,造成效率低下;第二个非空判断是为了避免实例属性已经赋值后,等待队列中的线程重复执行对象创建与赋值。而DCL可以保证多线程下只会进行一次对象初始化。但是这样的代码还是会有缺陷。
缺陷
指令集
让我们单独写一个对象创建方法,看一下这个操作对应的指令集是什么
public void aa(){
        singletonDemo = new SingletonDemo();
    }
先对这个类进行编译,然后使用idea 的 jclasslib插件对这个类进行查看,指令集如下:
  
指令分别是 new 、dup、invokespecial、putstatic,最后return返回,这里dup是复制操作,也就是对栈顶的元素复制一份,这里可以忽略不看。所以关键的指令是三个,1、new(实例化,对对象进行声明,分配地址),2、invokespecial(初始化,调用构造方法,进行属性赋值),3、putstatic(将这个对象赋值给属性singletonDemo)。
指令重排
单线程下存在着指令重排,什么是指令重排,比如说代码 x=1; y=1; x++; y++; y=x+y; JVM在加载时可能先加载到 y,那么它不会再去等待x加载,直接去执行 y++ ,这样就提高了运算效率,这种代码冲排序就是指令重排。但同时指令重排也不是随意的重排,它会遵守数据依赖性,比如虽然先加载了y,执行了 y++ ,也加载了 x,但是并不会接着去执行 y=x+y;因为右边操作的y 在前面的 y++修改了值,所以产生了对y++数据依赖,JVM并不会允许这样的指令重排(其实这个例子里的x++,y++指令会划分为三步,这里只需要知道表达的意思就可以了)。但是在多线程下数据依赖性就不能保证线程安全问题了。
回到前面的对象创建的指令集,2和3因为不存在数据依赖所以可能发生指令重排,所以在多线程下,可能线程1在执行new指令后直接执行指令3,线程2就执行到第7行第一个非空判断了,此时因为对象地址分配了,所以判断是非空,直接return,但是此时还没有执行初始化指令,所以该对象只是分配了空间还没有创建完对象,导致这个方法还是返回了一个null值(这个null值表示的是该位置的对象实际是获取不到的但是判断是非空的)。
解决
volatile 关键字可以禁止指令重排和保证可见性,但是由于不能保证原子性,所以在这里还是需要配合 synchronized 来使用。关于volatile在多线程基础里面说到了,所以这里最终的代码是:
 1 public class SingletonDemo {
 2     private volatile static SingletonDemo singletonDemo = null;
 3     private SingletonDemo(){
 4     }
 5
 6     public SingletonDemo getSingletonDemo(){
 7         if(singletonDemo == null){
 8             synchronized (SingletonDemo.class){
 9                 if(singletonDemo == null){
10                     singletonDemo = new SingletonDemo();
11                 }
12             }
13         }
14         return singletonDemo;
15     }
16 }
补充
单例模式的其他实现:
饿汉式
由于饿汉式是类加载时就会将对象实例创建赋值完成,所以在多线程下也是安全的,所以它的优点是不存在线程安全问题,缺点是没有延迟加载的优势,比如这个单例模式对象是一开始就加载好的,但是整个程序执行过程中过了很久才用上,那么从类被加载时就创建在堆中,一直到被用上,在堆中是一直占用空间的,如果存在多个饿汉式的单例类,就无形提高了GC发生的次数。降低程序的性能。
1、直接实例化饿汉式
public class Singleton1 {
    private static final Singleton1 INSTANCE=new Singleton1();
    private Singleton1() {
    }
    public static Singleton1 getSingleton() {
        return INSTANCE;
    }
}
特点:简单直接
2、枚举式饿汉式
public enum Singleton12 {
    INSTANCE;
    public void aa() {            //要调用的方法
    }
}
特点:最简洁
3、静态代码块饿汉式
public class Singleton13 {
    private static final Singleton13 INSTANCE;
    static {
        INSTANCE=new Singleton13();
    }
    public static Singleton13 getSingleton() {
        return INSTANCE;
    }
}
特点:可以在类初始化时增加其他操作
懒汉式
懒汉式单例模式就是直到调用方法去获取对象时才会创建对象,会有线程安全问题,所以更为复杂,但是因为是延迟加载,所以会有延迟加载的优势。
1、DCL懒汉式
代码如上。
2、静态内部类懒汉式
public class Singleton22 {
    private Singleton22() {
    }
    private static class Inner{
        private static final Singleton22 INSTANCE=new Singleton22();
    }
    public static Singleton22 getInstance() {
        return Inner.INSTANCE;
    }
}
相比于DCL懒汉式更加简单,同时也没有加锁解锁操作,更加高效。
DCL单例模式中的缺陷及单例模式的其他实现的更多相关文章
- 线程的同步之Synchronized在单例模式中的应用
		
synchronized在单例模式中的使用 在单例模式中有一种懒汉式的单例,就是类初始化的时候不创建对象.等第一次获取的时候再创建对象.这种单例在单线程下是没有问题的获取的也都是同一个对象.但是如果放 ...
 - java中的几种单例模式
		
目前比较常见的有4种(DCL为懒汉模式的线程安全版本). 单例模式的实现一般需要满足以下条件: 1.构造方法私有化,实例属性私有化. 2.必须仅在类的内部完成实例的初始化过程. 3.提供公共静态方法, ...
 - 1、c#中可以有静态构造方法,而java中没有,例如在单例模式中c#可以直接在静态构造中实例化对象,而java不可以
		
1.c#中可以有静态构造方法,而java中没有,例如在单例模式中c#可以直接在静态构造中实例化对象,而java不可以
 - PHP中的设计模式:单例模式(译)
		
原文链接:http://coderoncode.com/2014/01/27/design-patterns-php-singletons.html 单例模式用于限制类实例化到单个对象,当整个系统只需 ...
 - Java中的五种单例模式实现方法
		
[代码] Java中的五种单例模式实现方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 2 ...
 - 关于Java单例模式中懒汉式和饿汉式的两种类创建方法
		
一. 什么是单例模式 因程序需要,有时我们只需要某个类同时保留一个对象,不希望有更多对象,此时,我们则应考虑单例模式的设计. 二. 单例模式的特点 1. 单例模式只能有一个实例. 2. 单例类必须创建 ...
 - 单例模式中的volatile关键字
		
在之前学习了单例模式在多线程下的设计,疑惑为何要加volatile关键字.加与不加有什么区别呢?这里我们就来研究一下.单例模式的设计可以参考个人总结的这篇文章 背景:在早期的JVM中,synchr ...
 - Spring中常见的设计模式——单例模式
		
一.单例模式的应用场景 单例模式(singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点.J2EE中的ServletContext,ServletCon ...
 - 关于Java单例模式中双重校验锁的实现目的及原理
		
开始复习设计模式,一开始理解单例模式中的双重校验锁卡住了,想通了后就自己做了段思维导图来帮助自己理解. 其实理解下来并不难,但还是记录下来帮助自己回忆和借机试试养成写博客的习惯~ public cla ...
 
随机推荐
- Matlab .asv文件
			
参考: https://blog.csdn.net/u013152895/article/details/44724199 有时在存放m文件的文件夹中会出现*.asv asv 就是auto save的 ...
 - spring-boot-route(九)整合JPA操作数据库
			
单调的增删改查让越来越多的程序员感到乏味,这时候就出现了很多优秀的框架,完成了对增删改查操作的封装,只需要简单配置,无需书写任何sql,就可以完成增删改查.这里比较推荐的是Spring Data Jp ...
 - 使用Appium进行iOS的真机自动化测试
			
windows不支持appium连接ios,只适用于mac 使用Appium进行iOS的真机自动化测试 安装类库 Homebrew 如果没有安装过Homebrew,先安装[ homebrew ] np ...
 - 设计完美windbg断点
			
说到现场调试,断点是最重要的.通常,在生产环境中解决一个非常复杂的问题需要在本地.非生产环境中调试我自己的一台测试机器.我通常会调试有问题的进程或代码,以便更好地了解它是如何工作的,以及在我进入时需要 ...
 - 二进制安装MySQL-5.7.28
			
系统基础优化 #更改主机名 hostname msyql echo "msyql" >/etc/hostname #修改字符集 echo "LANG="z ...
 - Nuxt|Vue仿探探/陌陌卡片式滑动|vue仿Tinder拖拽翻牌效果
			
探探/Tinder是一个很火的陌生人社交App,趁着国庆假期闲暇时间倒腾了个Nuxt.js项目,项目中有个模块模仿探探滑动切换界面效果.支持左右拖拽滑动like和no like及滑动回弹效果. 一览效 ...
 - dubbo使用问题
			
新入职此公司, 发现公司使用的框架原来是传说中的分布式的(原谅我以前在传统公司工作,并远离浪潮久矣), 使用过程中发现各服务之间使用 dubbo 进行通信. 特地总结下遇见的坑,为以后总结经验. ...
 - Python+Appium自动化测试(5)-appium元素定位常用方法
			
对于Android而言,查找appUI界面元素属性的工具有三种:appium desktop,uiautomatorviewer.bat,weditor.之前已经介绍过了weditor的使用,这里我将 ...
 - Acticiti流程引擎在已知当前流程定义id的情况下获取当前流程的所有信息(包括:节点和连线)
			
这里我们已知流程已经部署,我的需求是获取当前流程的所有任务节点,我使用instanceof关键字来进行匹配 private List<UserTask> getProcessUserTas ...
 - 多测师讲解——python002.2练习题
			
# # 作业:第二天# ## # 1.求出1 / 1 + 1 / 3 + 1 / 5--+1 / 99的和 (1分之一+1分之三+1分支5....)# # 2.用循环语句,计算2 - 10之间整数的循 ...