漫画:什么是单例设计模式

转载自 永远爱大家的 程序员小灰





—————  第二天  —————















单例模式第一版:

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
    private Singleton() {}  //私有构造函数
    private static Singleton instance = null;  //单例对象
    //静态工厂方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}


为什么这样写呢?我们来解释几个关键点:


1、要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。


2、instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。


3、getInstance是获取单例对象的方法。


如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。


如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式


这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。







为什么说刚才的代码不是线程安全呢?


假设Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance方法:




因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:





这样一来,显然instance被构建了两次。让我们对代码做一下修改:



单例模式第二版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
    private Singleton() {}  //私有构造函数
   private static Singleton instance = null;  //单例对象
   //静态工厂方法
   public static Singleton getInstance() {
        if (instance == null) {      //双重检测机制
         synchronized (Singleton.class){  //同步锁
           if (instance == null) {     //双重检测机制
             instance = new Singleton();
               }
            }
         }
        return instance;
    }
}


为什么这样写呢?我们来解释几个关键点:


1、为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。


2、进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。












像这样两次判空的机制叫做双重检测机制













————————————











假设这样的场景,当两个线程一先一后访问getInstance方法的时候,当A线程正在构建对象,B线程刚刚进入方法:




这种情况表面看似没什么问题,要么Instance还没被线程A构建,线程B执行 if(instance == null)的时候得到false;要么Instance已经被线程A构建完成,线程B执行 if(instance == null)的时候得到true。


真的如此吗?答案是否定的。这里涉及到了JVM编译器的指令重排


指令重排是什么意思呢?比如java中简单的一句 instance = new Singleton,会被编译器编译成如下JVM指令:


memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置instance指向刚分配的内存地址 


但是这些指令顺序并非一成不变,有可能会经过JVM和CPU的优化,指令重排成下面的顺序:


memory =allocate();    //1:分配对象的内存空间 

instance =memory;     //3:设置instance指向刚分配的内存地址 

ctorInstance(memory);  //2:初始化对象 


当线程A执行完1,3,时,instance对象还未完成初始化,但已经不再指向null。此时如果线程B抢占到CPU资源,执行  if(instance == null)的结果会是false,从而返回一个没有初始化完成的instance对象。如下图所示:







如何避免这一情况呢?我们需要在instance对象前面增加一个修饰符volatile。



单例模式第三版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
    private Singleton() {}  //私有构造函数
   private volatile static Singleton instance = null;  //单例对象
   //静态工厂方法
   public static Singleton getInstance() {
       if (instance == null) {      //双重检测机制
       synchronized (this){  //同步锁
         if (instance == null) {     //双重检测机制
           instance = new Singleton();
             }
          }
       }
       return instance;
    }
}






The volatile keyword indicates that a value may change between different accesses, it prevents an optimizing compiler from optimizing away subsequent reads or writes and thus incorrectly reusing a stale value or omitting writes.





经过volatile的修饰,当线程A执行instance = new Singleton的时候,JVM执行顺序是什么样?始终保证是下面的顺序:


memory =allocate();    //1:分配对象的内存空间 

ctorInstance(memory);  //2:初始化对象 

instance =memory;     //3:设置instance指向刚分配的内存地址 


如此在线程B看来,instance对象的引用要么指向null,要么指向一个初始化完毕的Instance,而不会出现某个中间态,保证了安全。









几点说明:


1、volatile关键字不但可以防止指令重排,也可以保证线程访问的变量值是主内存中的最新值。有关volatile的详细原理,我在以后的漫画中会专门讲解。


2、本漫画纯属娱乐,还请大家尽量珍惜当下的工作,切勿模仿小灰的行为哦。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在工作繁重、睡眠较少的情况下保持旺盛精力?

作者:陈炬 链接:https://www.zhihu.com/question/23177623/answer/47785761 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 本人也在创业,结合《精力管理》一书,说说我…

mockito接口没法赋值_Mockito:无法实例化@InjectMocks字段:类型是接口

mockito接口没法赋值使用Mockito进行Java类的模拟和存根的任何人,可能都熟悉InjectMocks -annotation。 在要测试的类上使用此批注,Mockito将尝试通过构造函数注入,setter注入或属性注入来注入模拟。 魔术成功了,它无声地失败了&a…

tomcat(10)安全性

【0】README0.0)本文部分描述转自“深入剖析tomcat”,旨在学习 tomcat(10)安全性 的基本知识;0.1)servlet技术支持通过配置部署描述器(web.xml)文件来对这些内容进行访问控制;(干货—…

SonarQube 8.3.x中的Maven项目的测试覆盖率报告

几年前,我写了一篇博客文章,介绍如何在SonarQube中生成测试报告,该报告独立于单元测试和集成测试的测试报告中。 从SonarQube 6.2开始,测试报告不再在这些类别中分开(请参阅SonarQube的博客文章 )。 SonarQ…

单例模式懒汉、饿汉和登记

转载自 JAVA设计模式之单例模式本文继续介绍23种设计模式系列之单例模式。 概念:  java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。  单例模式有以下特点…

量角器中Selenium定位器的完整指南(示例)

在测试网站的功能时,特别是Web元素(例如单选按钮,文本框,下拉列表等),您需要确保能够访问这些元素。 Selenium定位器正是出于这个目的,通过使用此命令,我们可以识别这些Web元素DOM&a…

MySQL的自然联结+外部联结(左外连接,右外连接)+内部联结

【0】README0.1)本文旨在review MySQL的自然联结外部联结(左外连接,右外连接)内部联结 的相关知识;【1】自然联结1)自然联结定义:无论何时对表进行联结,应该至少有一个列出现不止一个…

MySQL 添加列+修改列+删除列

【0】REAMDE 0.1)本文部分文字描述转自 http://blog.163.com/zhangjie_0303/blog/static/99082706201191911653778/ 0.2)本文旨在review mysql 对列的相关操作:如添加,修改,删除以及重命名表名等操作; 【1】…

compose应用_带有PostgreSQLDocker Compose for Spring Boot应用程序

compose应用在此博客文章中,您将学习如何使用PostgreSQL配置Spring Boot应用程序以与Docker Compose一起运行。 这篇博客文章涵盖: Spring Boot应用程序Dockerfile配置,在依赖项和资源之间进行了清晰的分离 用于通过PostgreSQL运行应用程序…

单例模式面试题

转载自 单例模式面试题(特点、理解) (1)单例模式特点(什么是单例模式)?  a.单例类只能有一个实例。  b.单例类必须自己创建自己的唯一实例。  c.单例类必须给所有其他对象提供这一实例。 (2)单例模式的作用&#x…

MySQL的source命令不加分号和delimiter的使用

【0】README 0.1)本文旨在 review source 命令, 这一直是我的痛,为什么一直导入 sql 文件不成功,一直没有写 blog 吧他 记录下来(事实上,也间接证明我就是个小白); 0.2&#xff09…

selenium自动化测试_维持Selenium测试自动化的完美方法

selenium自动化测试毫无疑问, 自动浏览器测试已改变了软件开发的工作方式。 如果不是Selenium,我们将无法像我们一样使用各种各样的无错误的Web应用程序。 但是有时,甚至IT部门也误解了自动化一词。 大多数人认为计算机将为他们完成所有测试…

单例模式的优与劣

转载自 大话设计模式(四)单例模式的优与劣前言首先来明确一个问题,那就是在某些情况下,有些对象,我们只需要一个就可以了,比如,一台计算机上可以连好几个打印机,但是这个计算机上的打印程序只能有一个&…

MySQL存储过程+游标+触发器

【0】README0.1)本文旨在 arrange mysql 存储过程及如何在存储中使用游标 的相关知识;0.2)delimieter的用法:参见 http://blog.csdn.net/pacosonswjtu/article/details/51407756;【1】存储过程基础1)intro…

java迭代器退出迭代_使用Java迭代器修改数据时要小心

java迭代器退出迭代随着本学期的结束,我想我会分享一个关于如何非常熟悉Java迭代器的小故事。 现实世界语境 就上下文而言,我开设了第二年软件组件课程,这是尝试进入该专业的学生的最后障碍。 当然,这门课程对学生来说压力很大&a…

pojo 带参构造函数_带有Java Pojo作为输入输出示例的AWS Lambda函数

pojo 带参构造函数在上一个教程中,我们看到了如何使用Java创建AWS Lambda函数,我们传递了String作为输入,还返回了String作为Output。如果您是第一次创建lambda函数,我建议先阅读该教程。 在本教程中,我们将看到如何传…

MySQL检索数据(过滤+通配符+正则表达式)

【0】README0.1)本文部分文字描述转自“MySQL 必知必会”,旨在review “MySQL的基础知识”;【1】检索数据1)检索单个列:select a_name from table_name;2)检索多个列:select a_name,b_name from…

创建者模式

转载自 设计模式之创建者模式创建者模式又叫建造者模式,是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重…

buildpack_使用Buildpack容器化Spring Boot应用程序

buildpack在本文中,我们将看到如何使用Buildpacks容器化Spring Boot应用程序。 在先前的一篇文章中,我讨论了Jib 。 Jib允许我们在不使用Dockerfile的情况下将任何Java应用程序构建为Docker映像。 现在,从Spring Boot 2.3开始,我们…

MySQL创建字段+数据处理函数+汇总数据(聚集函数)+分组数据

【0】README0.1)本文部分文字描述转自“MySQL 必知必会”,旨在review“MySQL创建字段数据处理函数汇总数据(聚集函数)分组数据” 的基础知识;【1】创建计算字段1)problemsolution1.1)problem&am…