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

转载自  JAVA设计模式之单例模式

本文继续介绍23种设计模式系列之单例模式。

概念:
  java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例、饿汉式单例、登记式单例。
  单例模式有以下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。


一、懒汉式单例

[java] view plaincopy
  1. //懒汉式单例类.在第一次调用的时候实例化自己   
  2. public class Singleton {  
  3.     private Singleton() {}  
  4.     private static Singleton single=null;  
  5.     //静态工厂方法   
  6.     public static Singleton getInstance() {  
  7.          if (single == null) {    
  8.              single = new Singleton();  
  9.          }    
  10.         return single;  
  11.     }  
  12. }  

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:


1、在getInstance方法上加同步

[java] view plaincopy
  1. public static synchronized Singleton getInstance() {  
  2.          if (single == null) {    
  3.              single = new Singleton();  
  4.          }    
  5.         return single;  
  6. }  

2、双重检查锁定

[java] view plaincopy
  1. public static Singleton getInstance() {  
  2.         if (singleton == null) {    
  3.             synchronized (Singleton.class) {    
  4.                if (singleton == null) {    
  5.                   singleton = new Singleton();   
  6.                }    
  7.             }    
  8.         }    
  9.         return singleton;   
  10.     }  

3、静态内部类

[java] view plaincopy
  1. public class Singleton {    
  2.     private static class LazyHolder {    
  3.        private static final Singleton INSTANCE = new Singleton();    
  4.     }    
  5.     private Singleton (){}    
  6.     public static final Singleton getInstance() {    
  7.        return LazyHolder.INSTANCE;    
  8.     }    
  9. }    
这种比上面1、2都好一些,既实现了线程安全,又避免了同步带来的性能影响。


二、饿汉式单例

[java] view plaincopy
  1. //饿汉式单例类.在类初始化时,已经自行实例化   
  2. public class Singleton1 {  
  3.     private Singleton1() {}  
  4.     private static final Singleton1 single = new Singleton1();  
  5.     //静态工厂方法   
  6.     public static Singleton1 getInstance() {  
  7.         return single;  
  8.     }  
  9. }  
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。


三、登记式单例(可忽略)

[java] view plaincopy
  1. //类似Spring里面的方法,将类名注册,下次从里面直接获取。  
  2. public class Singleton3 {  
  3.     private static Map<String,Singleton3> map = new HashMap<String,Singleton3>();  
  4.     static{  
  5.         Singleton3 single = new Singleton3();  
  6.         map.put(single.getClass().getName(), single);  
  7.     }  
  8.     //保护的默认构造子  
  9.     protected Singleton3(){}  
  10.     //静态工厂方法,返还此类惟一的实例  
  11.     public static Singleton3 getInstance(String name) {  
  12.         if(name == null) {  
  13.             name = Singleton3.class.getName();  
  14.             System.out.println("name == null"+"--->name="+name);  
  15.         }  
  16.         if(map.get(name) == null) {  
  17.             try {  
  18.                 map.put(name, (Singleton3) Class.forName(name).newInstance());  
  19.             } catch (InstantiationException e) {  
  20.                 e.printStackTrace();  
  21.             } catch (IllegalAccessException e) {  
  22.                 e.printStackTrace();  
  23.             } catch (ClassNotFoundException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.         }  
  27.         return map.get(name);  
  28.     }  
  29.     //一个示意性的商业方法  
  30.     public String about() {      
  31.         return "Hello, I am RegSingleton.";      
  32.     }      
  33.     public static void main(String[] args) {  
  34.         Singleton3 single3 = Singleton3.getInstance(null);  
  35.         System.out.println(single3.about());  
  36.     }  
  37. }  

 登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。 

这里我对登记式单例标记了可忽略,我的理解来说,首先它用的比较少,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。


饿汉式和懒汉式区别

从名字上来说,饿汉和懒汉,

饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

另外从以下两点再区分以下这两种方式:


1、线程安全:

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。



2、资源加载和性能:

饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

至于1、2、3这三种实现又有些区别,

第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。


什么是线程安全?

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。


应用

以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式

[java] view plaincopy
  1. public class TestSingleton {  
  2.     String name = null;  
  3.   
  4.         private TestSingleton() {  
  5.     }  
  6.   
  7.     private static volatile TestSingleton instance = null;  
  8.   
  9.     public static TestSingleton getInstance() {  
  10.            if (instance == null) {    
  11.              synchronized (TestSingleton.class) {    
  12.                 if (instance == null) {    
  13.                    instance = new TestSingleton();   
  14.                 }    
  15.              }    
  16.            }   
  17.            return instance;  
  18.     }  
  19.   
  20.     public String getName() {  
  21.         return name;  
  22.     }  
  23.   
  24.     public void setName(String name) {  
  25.         this.name = name;  
  26.     }  
  27.   
  28.     public void printInfo() {  
  29.         System.out.println("the name is " + name);  
  30.     }  
  31.   
  32. }  

可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢,原因已经在下面评论中提到,

还有疑问可参考http://www.iteye.com/topic/652440
和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html


[java] view plaincopy
  1. public class TMain {  
  2.     public static void main(String[] args){  
  3.         TestStream ts1 = TestSingleton.getInstance();  
  4.         ts1.setName("jason");  
  5.         TestStream ts2 = TestSingleton.getInstance();  
  6.         ts2.setName("0539");  
  7.           
  8.         ts1.printInfo();  
  9.         ts2.printInfo();  
  10.           
  11.         if(ts1 == ts2){  
  12.             System.out.println("创建的是同一个实例");  
  13.         }else{  
  14.             System.out.println("创建的不是同一个实例");  
  15.         }  
  16.     }  
  17. }  

 运行结果:


结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。

对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。


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

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

相关文章

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

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

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

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

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

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

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

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

单例模式面试题

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

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

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

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

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

单例模式的优与劣

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

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

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

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

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

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

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

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

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

创建者模式

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

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

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

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

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

apache.camel_Apache Camel 3.2 – Camel的无反射配置

apache.camel在Apache Camel项目中&#xff0c;我们正在努力开发下一个即将发布的Apache Camel 3.2.0版本。 我们在Camel 3中努力研究的问题之一就是使其变得更小&#xff0c;更快。 其中一个方面是配置管理。 您可以按照12要素原则以多种方式完全配置Camel&#xff0c;以使配…

MySQL数据检索+查询+全文本搜索

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL数据检索查询全文本搜索” 的基础知识&#xff1b;【1】使用子查询1&#xff09;查询定义&#xff1a;任何sql 语句都是查询。但此术语一般指 select语句&#xff1b;SQL 还允许…

selenium自动化测试_49自动化测试中最常见的Selenium异常

selenium自动化测试开发人员将始终在编写代码时牢记不同的场景&#xff0c;但是在某些情况下&#xff0c;实现可能无法按预期工作。 相同的原则也适用于测试代码&#xff0c;该代码主要用于测试现有产品的功能&#xff0c;发现bug并确保产品100&#xff05;不受bug影响。 正确…

MySQL的CRUD操作+使用视图

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL数据检索查询全文本搜索” 的基础知识&#xff1b;【1】插入数据1&#xff09;insert是用来插入&#xff08;或添加&#xff09;行到数据库表的。插入可以用以下几种方式使用&a…

mega x_[MEGA DEAL] 2020年完整的Java Master Class Bundle(96%)

mega x通过超过62个小时的培训来掌握最流行的编程语言&#xff0c;从而树立信誉良好的开发人员职业 嘿&#xff0c;怪胎&#xff0c; 这一周&#xff0c;我们JCG促销专区 &#xff0c;我们有另一个极端的报价 。我们正在提供一个巨大的96&#xff05;off的完整2020 Python编程…