单例模式为何要出现

  在工作过程中,发现所有可以使用单例模式的类都有一个共性,那就是这个类没有自己的状态,换句话说,这些类无论你实例化多少个,其实都是一样的。

  如果我们不将这个类控制成单例的结构,应用中就会存在很多一模一样的类实例,这会非常浪费系统的内存资源,而且容易导致错误甚至一定会产生错误,

所以我们单例模式所期待的目标或者说使用它的目的,是为了尽可能的节约内存空间,减少无谓的GC消耗,并且使应用可以正常运作。

常见应用场景

  Windows的Task Manager(任务管理器)就是很典型的单例模式

  windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。

  网站的计数器,一般也是采用单例模式实现,否则难以同步。

  应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

  数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。

  操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

  Application 也是单例的典型应用(Servlet编程中会涉及到)

  在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理

  在servlet编程中,每个Servlet也是单例

  在spring MVC框架/struts1框架中,控制器对象也是单例

单例模式的优点

  由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

一、饿汉式实现(单例对象立即加载)

  1. public class SingletonDemo1 {
  2.  
  3. //类初始化时,立即加载这个对象(没有延时加载的优势)。加载类时,天然的是线程安全的!
  4. private static SingletonDemo1 instance = new SingletonDemo1();
  5.  
  6. private SingletonDemo1(){
  7. }
  8.  
  9. //方法没有同步,调用效率高!
  10. public static SingletonDemo1 getInstance(){
  11. return instance;
  12. }
  13.  
  14. }

饿汉式单例模式代码中,static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题。

因此,可以省略synchronized关键字。

问题:如果只是加载本类,而不是要调用getInstance(),甚至永远没有调用,则会造成资源浪费!

二、懒汉式实现(单例对象延迟加载)

  1. public class SingletonDemo2 {
  2.  
  3. //类初始化时,不初始化这个对象(延时加载,真正用的时候再创建)。
  4. private static SingletonDemo2 instance;
  5.  
  6. private SingletonDemo2(){ //私有化构造器
  7. }
  8.  
  9. //方法同步,调用效率低!
  10. public static synchronized SingletonDemo2 getInstance(){
  11. if(instance==null){
  12. instance = new SingletonDemo2();
  13. }
  14. return instance;
  15. }
  16.  
  17. }

要点:
    lazy load! 延迟加载, 懒加载! 真正用的时候才加载!
问题:
    资源利用率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。

三、双重检查锁实现单例模式

  1. public class SingletonDemo3 {
  2.  
  3. private static SingletonDemo3 instance = null;
  4.  
  5. public static SingletonDemo3 getInstance() {
  6. if (instance == null) {
  7. SingletonDemo3 sc;
  8. synchronized (SingletonDemo3.class) {
  9. sc = instance;
  10. if (sc == null) {
  11. synchronized (SingletonDemo3.class) {
  12. if(sc == null) {
  13. sc = new SingletonDemo3();
  14. }
  15. }
  16. instance = sc;
  17. }
  18. }
  19. }
  20. return instance;
  21. }
  22.  
  23. private SingletonDemo3() {
  24.  
  25. }
  26.  
  27. }

这个模式将同步内容下方到if内部,提高了执行的效率不必每次获取对象时都进行同步,只有第一次才同步创建了以后就没必要了。

问题:

由于编译器优化原因和JVM底层内部模型原因,

偶尔会出问题。不建议使用。

四、静态内部类实现方式(也是一种懒加载方式)

  1. public class SingletonDemo4 {
  2.  
  3. private static class SingletonClassInstance {
  4. private static final SingletonDemo4 instance = new SingletonDemo4();
  5. }
  6.  
  7. private SingletonDemo4(){
  8. }
  9.  
  10. //方法没有同步,调用效率高!
  11. public static SingletonDemo4 getInstance(){
  12. return SingletonClassInstance.instance;
  13. }
  14.  
  15. }

要点:

外部类没有static属性,则不会像饿汉式那样立即加载对象。

只有真正调用getInstance(),才会加载静态内部类。加载类时是线程 安全的。 instance是static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性.

兼备了并发高效调用和延迟加载的优势!

五、使用枚举实现单例模式

  1. public enum SingletonDemo5 {
  2.  
  3. //这个枚举元素,本身就是单例对象!
  4. INSTANCE;
  5.  
  6. //添加自己需要的操作!
  7. public void singletonOperation(){
  8. }
  9.  
  10. }

优点: 实现简单 枚举本身就是单例模式。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!

缺点: 无延迟加载

常见的五种单例模式在多线程环境下的效率测试

CountDownLatch

同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

countDown() 当前线程调此方法,则计数减一(建议放在 finally里执行)

await(), 调用此方法会一直阻塞当前线程,直到计时器的值为0

  1. public static void main(String[] args) throws Exception {
  2.  
  3. long start = System.currentTimeMillis();
  4. int threadNum = 10;
  5. final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
  6.  
  7. for(int i=0;i<threadNum;i++){
  8. new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. for(int i=0;i<1000000;i++){
  12. //Object o = SingletonDemo4.getInstance();
  13. Object o = SingletonDemo5.INSTANCE;
  14. }
  15. countDownLatch.countDown();
  16. }
  17. }).start();
  18. }
  19. countDownLatch.await(); //main线程阻塞,直到计数器变为0,才会继续往下执行!
  20. long end = System.currentTimeMillis();
  21. System.out.println("总耗时:"+(end-start));
  22. }

总结

主要:

饿汉式(线程安全,调用效率高。 但是,不能延时加载。)

懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)

其他:

双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)

静态内部类式(线程安全,调用效率高。 但是,可以延时加载)

枚举式(线程安全,调用效率高,不能延时加载。并且可以天然的防止反射和反序列化漏洞!)

如何选用?

单例对象 占用 资源 少,不需要 延时加载:

枚举式 好于 饿汉式

单例对象 占用 资源 大,需要 延时加载:

静态内部类式 好于 懒汉式

① 设计模式的艺术-01.单例(Singleton)模式的更多相关文章

  1. 设计模式C++描述----01.单例(Singleton)模式

    一.概念 单例模式:其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享. class CSingleton { //公有的静态方法,来获取该实例 public: s ...

  2. JavaScript 设计模式之----单体(单例)模式

    设计模式之--单体(单例)模式 1.介绍 从本章开始,我们会逐步介绍在JavaScript里使用的各种设计模式实现,在这里我不会过多地介绍模式本身的理论,而只会关注实现.OK,正式开始. 在传统开发工 ...

  3. 漫谈设计模式(二):单例(Singleton)模式

    1.前言 实际业务中,大多业务类只需要一个对象就能完成所有工作,另外再创建其他对象就显得浪费内存空间了,例如web开发中的servlet,这时便要用到单例模式,就如其名一样,此模式使某个类只能生成唯一 ...

  4. Android与设计模式——单例(Singleton)模式

    概念: java中单例模式是一种常见的设计模式.单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类仅仅能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. ...

  5. JAVA中实现单例(Singleton)模式的八种方式

    单例模式 单例模式,是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例.即一个类只有一个对象实例. 基本的实现思路 单 ...

  6. 【Java学习笔记之三十】详解Java单例(Singleton)模式

    概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式单例. 单例模式有以下特点: 1.单例类只能有一个实例. 2.单例类必须自己创建 ...

  7. 设计一个线程安全的单例(Singleton)模式

    在设计单例模式的时候.尽管非常easy设计出符合单例模式原则的类类型,可是考虑到垃圾回收机制以及线程安全性.须要我们思考的很多其它.有些设计尽管能够勉强满足项目要求,可是在进行多线程设计的时候.不考虑 ...

  8. 单例Singleton模式的两种实现方法

    在设计模式中,有一种叫Singleton模式的,用它可以实现一次只运行一个实例.就是说在程序运行期间,某个类只能有一个实例在运行.这种模式用途比较广泛,会经常用到,下面是Singleton模式的两种实 ...

  9. Java设计模式透析之 —— 单例(Singleton)

    写软件的时候经常需要用到打印日志功能,可以帮助你调试和定位问题,项目上线后还可以帮助你分析数据.但是Java原生带有的System.out.println()方法却很少在真正的项目开发中使用,甚至像f ...

随机推荐

  1. 【IdentityServer4文档】- 启动和概览

    启动和概览 有两种基本的方式来启动一个新的 IdentityServer 项目: 从空项目开始(从头开始) 从 Visual Studio 的 ASP.NET Identity 模板开始 假如您从头开 ...

  2. BETA阶段第一天

    1.提供当天站立式会议照片一张 2.每个人的工作 今天完成工作 林一心 服务器调试 张杭镖 数据库调整 赵意 前端设计 江鹭涛 前端设计 3.发布项目燃尽图 4.每日每人总结 林一心:服务器端的配置不 ...

  3. Java中的网络编程-3

    用户数据协议(UDP)是网络信息传输的另外一种形式, 基于UDP的通信不同于基于TCP的通信, 基于UDP的信息传递更快, 但是不提供可靠的保证. 使用UDP传输数据时, 用户无法知道数据能否正确地到 ...

  4. 小记IptabLes和IptabLex病毒清理过程

    去年有台Linux服务器被黑了,看了500万行日志(现在觉得当时好厉害呀),反正当时的日志文件有700Mb以上大.前两天师兄告诉我,信息中心的老师给他说我们有台服务器应该是被人入侵了,当作内网的跳板, ...

  5. Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction

    我在update数据库的时候出现的死锁 数据库表死锁 Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackExcept ...

  6. 使用nginx反向代理时,如何正确获取到用户的真实ip

    在记录日志的的时候,获取用户的信息,比如用户的ip,浏览器等等信息是十分重要的. 但是在使用nginx反向代理的时候,可能经过转发无法获取到用户的真实的ip, 在此情况下需要配置nginx,让其在转发 ...

  7. LoadRunner脚本增强技巧之自动关联

    为什么要做关联,原理很简单,录制脚本的时候,服务器会给用户一个唯一的认证码来进行操作,当再次回放脚本的时候服务器又会给一个全新的认证码,而录制好的脚本是写死的,还是拿老的认证码提交,肯定会导致脚本执行 ...

  8. 第78天:jQuery事件总结(一)

    jQuery事件总结(一)    现在就一点一点积累自己的知识体系,记录自己学到的和自己所理解的jQuery. JavaScript和HTML之间的交互式通过用户和浏览器操作页面时引发的事件机制来处理 ...

  9. python写BMI指数菜单

    需求: # 1.创建并输出菜单, 菜单是不可变的. 所以使用元组menus = ("1, 录入", "2, 查询", "3, 删除", &q ...

  10. 前端开发学习之——利用模板实现涉及url问题时的bug分析及解决(chrome源码)

    例如我们要实现如下页面,其中历史页面列表想来自底层返回的数据,此处用testData代替: 最初我写的实现代码如下: html文件: <!doctype html> <html cl ...