简介

双重检测锁定模式是一种设计模式,我们通过首次检测锁定条件而不是实际获得锁从而减少获取锁的开销。

双重检查锁定模式用法通常用于实现执行延迟初始化的单例工厂模式。延迟初始化推迟了成员字段或成员字段引用的对象的构造,直到实际需要才真正的创建。

但是我们需要非常小心的使用双重检测模式,以避免发送错误。

单例模式的延迟加载

先看一个在单线程正常工作的单例模式:

  1. public class Book {
  2. private static Book book;
  3. public static Book getBook(){
  4. if(book==null){
  5. book = new Book();
  6. }
  7. return book;
  8. }
  9. }

上面的类中定义了一个getBook方法来返回一个新的book对象,返回对象之前,我们先判断了book是否为空,如果不为空的话就new一个book对象。

初看起来,好像没什么问题,我们仔细考虑一下:

book=new Book()其实一个复杂的命令,并不是原子性操作。它大概可以分解为1.分配内存,2.实例化对象,3.将对象和内存地址建立关联。

在多线程环境中,因为重排序的影响,我们可能的到意向不到的结果。

最简单的办法就是加上synchronized关键字:

  1. public class Book {
  2. private static Book book;
  3. public synchronized static Book getBook(){
  4. if(book==null){
  5. book = new Book();
  6. }
  7. return book;
  8. }
  9. }

double check模式

如果要使用double check模式该怎么做呢?

  1. public class BookDLC {
  2. private static BookDLC bookDLC;
  3. public static BookDLC getBookDLC(){
  4. if(bookDLC == null ){
  5. synchronized (BookDLC.class){
  6. if(bookDLC ==null){
  7. bookDLC=new BookDLC();
  8. }
  9. }
  10. }
  11. return bookDLC;
  12. }
  13. }

我们先判断bookDLC是否为空,如果为空,说明需要实例化一个新的对象,这时候我们锁住BookDLC.class,然后再进行一次为空判断,如果这次不为空,则进行初始化。

那么上的代码有没有问题呢?

有,bookDLC虽然是一个static变量,但是因为CPU缓存的原因,我们并不能够保证当前线程被赋值之后的bookDLC,立马对其他线程可见。

所以我们需要将bookDLC定义为volatile,如下所示:

  1. public class BookDLC {
  2. private volatile static BookDLC bookDLC;
  3. public static BookDLC getBookDLC(){
  4. if(bookDLC == null ){
  5. synchronized (BookDLC.class){
  6. if(bookDLC ==null){
  7. bookDLC=new BookDLC();
  8. }
  9. }
  10. }
  11. return bookDLC;
  12. }
  13. }

静态域的实现

  1. public class BookStatic {
  2. private static BookStatic bookStatic= new BookStatic();
  3. public static BookStatic getBookStatic(){
  4. return bookStatic;
  5. }
  6. }

JVM在类被加载之后和被线程使用之前,会进行静态初始化,而在这个初始化阶段将会获得一个锁,从而保证在静态初始化阶段内存写入操作将对所有的线程可见。

上面的例子定义了static变量,在静态初始化阶段将会被实例化。这种方式叫做提前初始化。

下面我们再看一个延迟初始化占位类的模式:


  1. public class BookStaticLazy {
  2. private static class BookStaticHolder{
  3. private static BookStaticLazy bookStatic= new BookStaticLazy();
  4. }
  5. public static BookStaticLazy getBookStatic(){
  6. return BookStaticHolder.bookStatic;
  7. }
  8. }

上面的类中,只有在调用getBookStatic方法的时候才会去初始化类。

ThreadLocal版本

我们知道ThreadLocal就是Thread的本地变量,它实际上是对Thread中的成员变量ThreadLocal.ThreadLocalMap的封装。

所有的ThreadLocal中存放的数据实际上都存储在当前线程的成员变量ThreadLocal.ThreadLocalMap中。

如果使用ThreadLocal,我们可以先判断当前线程的ThreadLocal中有没有,没有的话再去创建。

如下所示:

  1. public class BookThreadLocal {
  2. private static final ThreadLocal<BookThreadLocal> perThreadInstance =
  3. new ThreadLocal<>();
  4. private static BookThreadLocal bookThreadLocal;
  5. public static BookThreadLocal getBook(){
  6. if (perThreadInstance.get() == null) {
  7. createBook();
  8. }
  9. return bookThreadLocal;
  10. }
  11. private static synchronized void createBook(){
  12. if (bookThreadLocal == null) {
  13. bookThreadLocal = new BookThreadLocal();
  14. }
  15. perThreadInstance.set(bookThreadLocal);
  16. }
  17. }

本文的代码:

learn-java-base-9-to-20/tree/master/security

本文已收录于 http://www.flydean.com/java-security-code-line-double-check-lock/

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

java安全编码指南之:锁的双重检测的更多相关文章

  1. java安全编码指南之:基础篇

    目录 简介 java平台本身的安全性 安全第一,不要写聪明的代码 在代码设计之初就考虑安全性 避免重复的代码 限制权限 构建可信边界 封装 写文档 简介 作为一个程序员,只是写出好用的代码是不够的,我 ...

  2. java安全编码指南之:Mutability可变性

    目录 简介 可变对象和不可变对象 创建mutable对象的拷贝 为mutable类创建copy方法 不要相信equals 不要直接暴露可修改的属性 public static fields应该被置位f ...

  3. java安全编码指南之:字符串和编码

    目录 简介 使用变长编码的不完全字符来创建字符串 char不能表示所有的Unicode 注意Locale的使用 文件读写中的编码格式 不要将非字符数据编码为字符串 简介 字符串是我们日常编码过程中使用 ...

  4. java安全编码指南之:输入校验

    目录 简介 在字符串标准化之后进行校验 注意不可信字符串的格式化 小心使用Runtime.exec() 正则表达式的匹配 简介 为了保证java程序的安全,任何外部用户的输入我们都认为是可能有恶意攻击 ...

  5. java安全编码指南之:可见性和原子性

    目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...

  6. java安全编码指南之:死锁dead lock

    目录 简介 不同的加锁顺序 使用private类变量 使用相同的Order 释放掉已占有的锁 简介 java中为了保证共享数据的安全性,我们引入了锁的机制.有了锁就有可能产生死锁. 死锁的原因就是多个 ...

  7. java安全编码指南之:lock和同步的正确使用

    目录 简介 使用private final object来作为lock对象 不要synchronize可被重用的对象 不要sync Object.getClass() 不要sync高级并发对象 不要使 ...

  8. java安全编码指南之:Thread API调用规则

    目录 简介 start一个Thread 不要使用ThreadGroup 不要使用stop()方法 wait 和 await 需要放在循环中调用 简介 java中多线程的开发中少不了使用Thread,我 ...

  9. java安全编码指南之:文件和共享目录的安全性

    目录 简介 linux下的文件基本权限 linux文件的特殊权限 Set UID 和 Set GID Sticky Bit SUID/SGID/SBIT权限设置 文件隐藏属性 特殊文件 java中在共 ...

随机推荐

  1. Broker的主从架构是怎么实现的?

    前言 上一篇文章我们一起聊了聊RocketMQ的NameServer的一些内部工作流程,了解了NameServer的部署和与Broker之间的联系,那么今天我们就来一起聊聊Broker的一些内部原理. ...

  2. DSRC和USRP的购买调研

    (转移自旧博客) 11.29 2019 实验室采购,所以进行了一定程度的调研. 主要包括两个Part,分别是DSRC和USRP的简单总结,购买建议和详细资料. Part.1 DSRC调研总结 1.1 ...

  3. ZT:做一个连自己都羡慕的人

    当你越来越杰出时,自然有人关注你, 当你越来越有能力时,自然会有人看得起你, 改变自己,你才有自信,梦想才会慢慢的实现. 做最好的自己,懒可以毁掉一个人,勤可以激发一个人! 不要等夕阳西下的时候才对自 ...

  4. 转载:使用ANSI转义码实现一个终端命令行界面

    下文转载自:https://baijiahao.baidu.com/s?id=1630601858000127124&wfr=spider&for=pc 习惯于使用Linux的人,时常 ...

  5. [PyTorch 学习笔记] 6.2 Normalization

    本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson6/bn_and_initialize.py https: ...

  6. jenkins打包前端项目报 error: index-pack died of signal 15 问题解决

    jenkins打包前端项目报 error: index-pack died of signal 15 问题解决 前几天用jenkins打包一个前端项目的时候出现了 error: index-pack ...

  7. Java审计之XSS篇

    Java审计之XSS篇 0x00 前言 继续 学习一波Java审计的XSS漏洞的产生过程和代码. 0x01 Java 中XSS漏洞代码分析 xss原理 xss产生过程: 后台未对用户输入进行检查或过滤 ...

  8. flutter driver 集成测试

    最近一直断断续续的学习flutter,今天跟大家介绍一下flutter driver测试. flutter测试基础 Flutter的测试遵循Android的测试规范进行了分层. 单元测试:测试单一功能 ...

  9. jmeter压测以及用Badboy录脚本

    一.压测时的基本配置: 1.设置线程数.延迟几秒.循环次数打勾表示一直去循环.调度器打勾可以填写持续时间.启动时间等 线程数:就是并发的用户数   N Ramp-Up Period(in second ...

  10. oracle之三目录库和辅助库

    目录库和辅助库 10.1 创建目录库(Catalog database)的必要性 如果没有catalog,RMAN的存储库(元数据)保存在目标库的控制文件里,这样可能存在如下隐患 1)目标库上的控制文 ...