双重检查锁模式导致空指针
今天遇到一个问题:莫名奇妙报了个空指针,后来发现原来单例模式在高并发下引起的:
双重检查锁模式的一般实现:
双重检查锁模式解决了单例、性能、线程安全问题,但是这种写法同样存在问题:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM
在实例化对象的时候会进行优化和指令重排序操作。
双重检查锁模式的一般实现
public class DoubleCheckLockMode {private static DoubleCheckLockMode instance;/*** 私有化构造函数*/private DoubleCheckLockMode(){}/*** 提供公开获取实例接口* @return*/public static DoubleCheckLockMode getInstance(){// 第一次判断,如果这里为空,不进入抢锁阶段,直接返回实例if (instance == null) {synchronized (DoubleCheckLockMode.class) {// 抢到锁之后再次判断是否为空if (instance == null) {instance = new DoubleCheckLockMode();}}}return instance;}
}
双重检查锁模式解决了单例、性能、线程安全问题,但是这种写法同样存在问题:在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM
在实例化对象的时候会进行优化和指令重排序操作
什么是指令重排?
private SingletonObject(){// 第一步int x = 10;// 第二步int y = 30;// 第三步Object o = new Object();
}
上面的构造函数SingletonObject()
,JVM
会对它进行指令重排序,所以执行顺序可能会乱掉,但是不管是那种执行顺序,JVM
最后都会保证所以实例都完成实例化。 如果构造函数中操作比较多时,为了提升效率,JVM
会在构造函数里面的属性未全部完成实例化时,就返回对象。双重检测锁出现空指针问题的原因就是出现在这里,当某个线程获取锁进行实例化时,其他线程就直接获取实例使用,由于JVM
指令重排序的原因,其他线程获取的对象也许不是一个完整的对象,所以在使用实例的时候就会出现空指针异常问题。
双重检查锁模式优化
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile
关键字,volatile
关键字严格遵循happens-before
原则,即:在读操作前,写操作必须全部完成
public class DoubleCheckLockModelVolatile {/*** 添加volatile关键字,保证在读操作前,写操作必须全部完成*/private static volatile DoubleCheckLockModelVolatile instance;/*** 私有化构造函数*/private DoubleCheckLockModelVolatile(){}/*** 提供公开获取实例接口* @return*/public static DoubleCheckLockModelVolatile getInstance(){if (instance == null) {synchronized (DoubleCheckLockModelVolatile.class) {if (instance == null) {instance = new DoubleCheckLockModelVolatile();}}}return instance;}
}
顺便复习一下设计模式:
一、设计模式
1.1 设计模式是什么?
- 设计模式是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。其目的是为了提高代码的可重用性、代码的可读性和代码的可靠性。
- 设计模式的本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性以及类的关联关系和组合关系的充分理解。
1.2 为什么要使用设计模式?
项目的需求是永远在变的,为了应对这种变化,使得我们的代码能够轻易的实现解耦和拓展
1.3 设计模式类型
- 创建型模式
创建型模式的主要关注点是怎样创建对象,它的主要特点是将对象的创建与使用分离。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。
- 结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。
- 行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。它分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。
创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|
单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式 | 适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式 | 模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式(责任链模式)、访问者模式 |
二、面向对象设计的六大设计原则
2.1 开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭
- 解读
- 用抽象构建框架,用实现扩展细节;
- 不以改动原有类的方式来实现新需求,而是应该以实现事先抽象出来的接口(或具体类继承抽象类)的方式来实现。
- 优点
- 可以在不改动原有代码的前提下给程序扩展功能,增加了程序的可扩展性;
- 同时也降低了程序的维护成本。
2.2 单一职责原则
一个类只允许有一个职责,即只有一个导致该类变更的原因。
- 解读
类职责的变化往往就是导致类变化的原因:也就是说如果一个类具有多种职责,就会有多种导致这个类变化的原因,从而导致这个类的维护变得困难;
往往在软件开发中随着需求的不断增加,可能会给原来的类添加一些本来不属于它的一些职责,从而违反了单一职责原则。如果我们发现当前类的职责不仅仅有一个,就应该将本来不属于该类真正的职责分离出去;
不仅仅是类,函数(方法)也要遵循单一职责原则,即:一个函数(方法)只做一件事情。如果发现一个函数(方法)里面有不同的任务,则需要将不同的任务以另一个函数(方法)的形式分离出去。
- 优点
- 提高代码的可读性,更实际性地更降低了程序出错的风险;
- 有利于bug的追踪,降低了程序的维护成本。
2.3 依赖倒置原则
- 依赖抽象,而不是依赖实现;
- 抽象不应该依赖细节;细节应该依赖抽象;
- 高层模块不能依赖低层模块,二者都应该依赖抽象。
- 解读
- 面向接口编程,而不是面向实现编程;
- 尽量不要从具体的类派生,而是以继承抽象类或实现接口来实现;
- 关于高层模块与低层模块的划分可以按照决策能力的高低进行划分。业务层自然就处于上层模块,逻辑层和数据层自然就归类为底层。
- 优点
- 通过抽象来搭建框架,建立类和类的关联,以减少类间的耦合性;
- 以抽象搭建的系统要比以具体实现搭建的系统更加稳定,扩展性更高,同时也便于维护。
- 里氏替换原则
子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
2.4 接口隔离原则
多个特定的客户端接口要好于一个通用性的总接口。
- 解读
- 客户端不应该依赖它不需要实现的接口;
- 不建立庞大臃肿的接口,应尽量细化接口,接口中的方法应该尽量少。
注意:接口的粒度也不能太小。如果过小,则会造成接口数量过多,使设计复杂化。
- 优点
避免同一个接口里面包含不同类职责的方法,接口责任划分更加明确,符合高内聚低耦合的思想。
2.5 迪米特法则(最少知道原则)
一个对象应该对尽可能少的对象有接触,也就是只接触那些真正需要接触的对象。
- 解读
一个类应该只和它的成员变量,方法的输入,返回参数中的类作交流,而不应该引入其他的类(间接交流)。
- 优点
可以良好地降低类与类之间的耦合,减少类与类之间的关联程度,让类与类之间的协作更加直接。
2.6 组合聚合复用原则
所有引用基类的地方必须能透明地使用其子类的对象,也就是说子类对象可以替换其父类对象,而程序执行效果不变。
-解读
在继承体系中,子类中可以增加自己特有的方法,也可以实现父类的抽象方法,但是不能重写父类的非抽象方法,否则该继承关系就不是一个正确的继承关系。
- 优点
可以检验继承使用的正确性,约束继承在使用上的泛滥。
关于设计模式更多的可以参考:
菜鸟教程:https://www.runoob.com/design-pattern/singleton-pattern.html
部分摘自:https://www.cnblogs.com/vandusty/p/11444293.html
双重检查锁模式导致空指针相关推荐
- java并发编程(二十六)——单例模式的双重检查锁模式为什么必须加 volatile?
前言 本文我们从一个问题出发来进行探究关于volatile的应用. 问题:单例模式的双重检查锁模式为什么必须加 volatile? 什么是单例模式 单例模式指的是,保证一个类只有一个实例,并且提供一个 ...
- 双重检查锁Double Checked Locking Pattern的非原子操作下的危险性
Double Checked Locking Pattern 即双重检查锁模式. 双重检查锁模式是一种软件设计模式,用于减少获取锁的开销.程序首先检查锁定条件,并且仅当检查表明需要锁时才才获取锁. 延 ...
- C++和双重检查锁定模式(DCLP)的风险
转自: http://blog.jobbole.com/86392/ 多线程其实就是指两个任务一前一后或者同时发生. 1 简介 当你在网上搜索设计模式的相关资料时,你一定会找到最常被提及的一个模式:单 ...
- 双重检查锁,原来是这样演变来的,你了解吗
最近在看Nacos的源代码时,发现多处都使用了"双重检查锁"的机制,算是非常好的实践案例.这篇文章就着案例来分析一下双重检查锁的使用以及优势所在,目的就是让你的代码格调更加高一个层 ...
- 双重检查锁单例模式为什么要用volatile关键字?
前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下v ...
- 双重检查锁为什么要使用volatile字段?
前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下v ...
- 双重检查锁与单例模式
单例模式是比较常见的一种设计模式,在开发实践中经常看到它的身影,它有很多种实现方式,曾经有人在一篇文章中列举了十几种实现方式,比如饿汉式.懒汉式.双重检查锁.枚举...等等,程序员应该都熟悉这些常见的 ...
- 双重检查锁(Double-Checked Locking)的缺陷
双重检查锁(Double-Checked Locking)的缺陷 第一种有问题的写法 第二种有问题的写法 第三种有问题的写法 它不起作用 它不起作用的第一个原因 一个测试用例显示它不起作用 一个不起作 ...
- java双重检查锁单例真的线程安全吗?
相信大多数同学在面试当中都遇到过手写单例模式的题目,那么如何写一个完美的单例是面试者需要深究的问题,因为一个严谨的单例模式说不定就直接决定了面试结果,今天我们就要来讲讲看似线程安全的双重检查锁单例模 ...
最新文章
- java 共享锁 独占锁_Java并发编程锁之独占公平锁与非公平锁比较
- 一篇文章,带你了解 “机器学习工程师” 必备技能图谱
- linux编译器6,Linux安装gcc编译器详解(CentOS 6.5 64位系统)
- 2021江苏地区高考成绩排名查询,2021年江苏高考成绩排名及一分一段表
- 用python简单处理图片(2):图像通道\几何变换\裁剪
- Android从url不产生cookie,如何从android.webkit.CookieManager获取所有cookie或cookie的URL
- 【2018.3.31】模拟赛之一-ssl2406 约数【水题】
- 第 7 节:前端面试指南 — 微信小程序篇(附面试题答案)
- mysql中对象标识符的命名规则,标准规范数据库命名规范.doc
- HDU 1394 Minimum Inversion Number 树状数组
- 如何在Adobe Illustrator中矢量化图像
- 游戏设计之路——游戏设计文档详解(GDD)
- 男生必学,与女生聊天技巧
- git删除远程创库命令
- 1259_STM32CubeProgrammer的简单使用
- 台式电脑win7旗舰版 怎么调节屏幕亮度 显示器太亮了!
- Mysql 将逗号隔开的属性字段数据由列转行
- C#调用硬件设备开发流程
- HTML5消消乐DEMO演示
- webstorm快捷键问题,求大神赐教
热门文章
- C++ Q16: dereferencing
- kafka0.9 java commit_0.9版本kafka优化及常见错误(转载)
- python3 xml 取标签显示内容_如何应用“XML+XSLT”技术分离Web表示层数据和样式
- mysql创建库和表确保utf8_mysql创建utf8数据库
- mysql varchar最多可以存多少汉字_MySql的这几个坑你踩过没?真是防不胜防!
- hdfs namenode -format 初始化创建不了目录的问题
- 九十、深入弹性(Flex)布局
- kda 处理曲面地图绘制的问题
- android跑步软件,手机跑步软件哪个好_安卓手机跑步记录软件_手机跑步app【最新】-太平洋电脑网...
- 图神经网络的二阶池化:从节点表示中学习图的表示