关于单例模式的文章,其实网上早就已经泛滥了。但一个小小的单例,里面却是有着许多的变化。网上的文章大多也是提到了其中的一个或几个点,很少有比较全面且脉络清晰的文章,于是,我便萌生了写这篇文章的念头。企图把这个单例说透,说深入。但愿我不会做的太差。

  首先来看一个典型的实现:

 1 /** 2  * 基础的单例模式,Lazy模式,非线程安全 3  * 优点:lazy,初次使用时实例化单例,避免资源浪费 4  * 缺点:1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题 5  * 2、非线程安全。多线程下可能会有多个实例被初始化。 6  *  7  * @author laichendong 8  * @since 2011-12-5 9  */10 public class SingletonOne {11     12     /** 单例实例变量 */13     private static SingletonOne instance = null;14     15     /**16      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。17*/18     private SingletonOne() {19         20     }21     22     /**23      * 获取单例对象实例24      * 25      * @return 单例对象26*/27     public static SingletonOne getInstance() {28         if (instance == null) { // 129             instance = new SingletonOne(); // 230         }31         return instance;32     }33     34 }

  注释中已经有简单的分析了。接下来分析一下关于“非线程安全”的部分。

  1、当线程A进入到第28行(#1)时,检查instance是否为空,此时是空的。
  2、此时,线程B也进入到28行(#1)。切换到线程B执行。同样检查instance为空,于是往下执行29行(#2),创建了一个实例。接着返回了。
  3、在切换回线程A,由于之前检查到instance为空。所以也会执行29行(#2)创建实例。返回。
  4、至此,已经有两个实例被创建了,这不是我们所希望的。

 怎么解决线程安全问题?

  方法一:同步方法。即在getInstance()方法上加上synchronized关键字。这时单例变成了  

使用同步方法的单例

 1 /** 2  * copyright © sf-express Inc 3  */ 4 package com.something.singleton; 5  6 /** 7  * 同步方法 的单例模式,Lazy模式,线程安全 8  * 优点: 9  * 1、lazy,初次使用时实例化单例,避免资源浪费10  * 2、线程安全11  * 缺点:12  * 1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题13  * 2、每次调用getInstance()都要获得同步锁,性能消耗。14  * 15  * @author laichendong16  * @since 2011-12-517  */18 public class SingletonTwo {19     20     /** 单例实例变量 */21     private static SingletonTwo instance = null;22     23     /**24      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。25 */26     private SingletonTwo() {27         28     }29     30     /**31      * 获取单例对象实例32      * 同步方法,实现线程互斥访问,保证线程安全。33      * 34      * @return 单例对象35 */36     public static synchronized SingletonTwo getInstance() {37         if (instance == null) { // 138             instance = new SingletonTwo(); // 239         }40         return instance;41     }42     43 }

  加上synchronized后确实实现了线程的互斥访问getInstance()方法。从而保证了线程安全。但是这样就完美了么?我们看。其实在典型实现里,会导致问题的只是当instance还没有被实例化的时候,多个线程访问#1的代码才会导致问题。而当instance已经实例化完成后。每次调用getInstance(),其实都是直接返回的。即使是多个线程访问,也不会出问题。但给方法加上synchronized后。所有getInstance()的调用都要同步了。其实我们只是在第一次调用的时候要同步。而同步需要消耗性能。这就是问题。

  方法二:双重检查加锁Double-checked locking。
  
其实经过分析发现,我们只要保证 instance = new SingletonOne(); 是线程互斥访问的就可以保证线程安全了。那把同步方法加以改造,只用synchronized块包裹这一句。就得到了下面的代码:

1     public static SingletonThree getInstance() {2         if (instance == null) { // 13             synchronized (SingletonThree.class) {4                 instance = new SingletonThree(); // 25             }6         }7         return instance;8     }

  这个方法可行么?分析一下发现是不行的!
  1、线程A和线程B同时进入//1的位置。这时instance是为空的。
  2、线程A进入synchronized块,创建实例,线程B等待。
  3、线程A返回,线程B继续进入synchronized块,创建实例。。。
  4、这时已经有两个实例创建了。

  为了解决这个问题。我们需要在//2的之前,再加上一次检查instance是否被实例化。(双重检查加锁)接下来,代码变成了这样:

 1     public static SingletonThree getInstance() { 2         if (instance == null) { // 1 3             synchronized (SingletonThree.class) { 4                 if (instance == null) {  5                     instance = new SingletonThree(); // 2 6                 } 7             } 8         } 9         return instance;10     }

  这样,当线程A返回,线程B进入synchronized块后,会先检查一下instance实例是否被创建,这时实例已经被线程A创建过了。所以线程B不会再创建实例,而是直接返回。貌似!到此为止,这个问题已经被我们完美的解决了。遗憾的是,事实完全不是这样!这个方法在单核和 多核的cpu下都不能保证很好的工作。导致这个方法失败的原因是当前java平台的内存模型。java平台内存模型中有一个叫“无序写”(out-of-order writes)的机制。正是这个机制导致了双重检查加锁方法的失效。这个问题的关键在上面代码上的第5行:instance = new SingletonThree(); 这行其实做了两个事情:1、调用构造方法,创建了一个实例。2、把这个实例赋值给instance这个实例变量。可问题就是,这两步jvm是不保证顺序的。也就是说。可能在调用构造方法之前,instance已经被设置为非空了。下面我们看一下出问题的过程:
  1、线程A进入getInstance()方法。
  2、因为此时instance为空,所以线程A进入synchronized块。
  3、线程A执行 instance = new SingletonThree(); 把实例变量instance设置成了非空。(注意,实在调用构造方法之前。)
  4、线程A退出,线程B进入。
  5、线程B检查instance是否为空,此时不为空(第三步的时候被线程A设置成了非空)。线程B返回instance的引用。(问题出现了,这时instance的引用并不是SingletonThree的实例,因为没有调用构造方法。) 
  6、线程B退出,线程A进入。
  7、线程A继续调用构造方法,完成instance的初始化,再返回。

  好吧,继续努力,解决由“无序写”带来的问题。

 1     public static SingletonThree getInstance() { 2         if (instance == null) {  3             synchronized (SingletonThree.class) {           // 1 4                 SingletonThree temp = instance;             // 2 5                 if (temp == null) { 6                     synchronized (SingletonThree.class) {   // 3 7                         temp = new SingletonThree();        // 4 8                     } 9                     instance = temp;                        // 510                 }11             }12         }13         return instance;14     }

  解释一下执行步骤。
  1、线程A进入getInstance()方法。
  2、因为instance是空的 ,所以线程A进入位置//1的第一个synchronized块。
  3、线程A执行位置//2的代码,把instance赋值给本地变量temp。instance为空,所以temp也为空。 
  4、因为temp为空,所以线程A进入位置//3的第二个synchronized块。
  5、线程A执行位置//4的代码,把temp设置成非空,但还没有调用构造方法!(“无序写”问题) 
  6、线程A阻塞,线程B进入getInstance()方法。
  7、因为instance为空,所以线程B试图进入第一个synchronized块。但由于线程A已经在里面了。所以无法进入。线程B阻塞。
  8、线程A激活,继续执行位置//4的代码。调用构造方法。生成实例。
  9、将temp的实例引用赋值给instance。退出两个synchronized块。返回实例。
  10、线程B激活,进入第一个synchronized块。
  11、线程B执行位置//2的代码,把instance实例赋值给temp本地变量。
  12、线程B判断本地变量temp不为空,所以跳过if块。返回instance实例。

  好吧,问题终于解决了,线程安全了。但是我们的代码由最初的3行代码变成了现在的一大坨~。于是又有了下面的方法。

  方法三:预先初始化static变量。

 1 /** 2  * 预先初始化static变量 的单例模式  非Lazy  线程安全 3  * 优点: 4  * 1、线程安全  5  * 缺点: 6  * 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。 7  *  8  * @author laichendong 9  * @since 2011-12-510  */11 public class SingletonFour {12     13     /** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */14     private static SingletonFour instance = new SingletonFour();15     16     /**17      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。18      */19     private SingletonFour() {20         21     }22     23     /**24      * 获取单例对象实例25      * 26      * @return 单例对象27      */28     public static SingletonFour getInstance() {29         return instance;30     }31     32 }

  看到这个方法,世界又变得清净了。由于java的机制,static的成员变量只在类加载的时候初始化一次,且类加载是线程安全的。所以这个方法实现的单例是线程安全的。但是这个方法却牺牲了Lazy的特性。单例类加载的时候就实例化了。如注释所述:非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。

  那到底有没有完美的办法?懒加载,线程安全,代码简单。

  方法四:使用内部类。

 1 /** 2  * 基于内部类的单例模式  Lazy  线程安全 3  * 优点: 4  * 1、线程安全 5  * 2、lazy 6  * 缺点: 7  * 1、待发现 8  *  9  * @author laichendong10  * @since 2011-12-511  */12 public class SingletonFive {13     14     /**15      * 内部类,用于实现lzay机制16 */17     private static class SingletonHolder{18         /** 单例变量  */19         private static SingletonFive instance = new SingletonFive();20     }21     22     /**23      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。24 */25     private SingletonFive() {26         27     }28     29     /**30      * 获取单例对象实例31      * 32      * @return 单例对象33 */34     public static SingletonFive getInstance() {35         return SingletonHolder.instance;36     }37     38 }

  解释一下,因为java机制规定,内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候实例化一次instance。

  最后,总结一下:
  1、如果单例对象不大,允许非懒加载,可以使用方法三。
  2、如果需要懒加载,且允许一部分性能损耗,可以使用方法一。(官方说目前高版本的synchronized已经比较快了)
  3、如果需要懒加载,且不怕麻烦,可以使用方法二。
  4、如果需要懒加载,没有且!推荐使用方法四。


  

转载于:https://www.cnblogs.com/coffee/archive/2011/12/05/inside-java-singleton.html

【深入】java 单例模式相关推荐

  1. Java单例模式个人总结(实例变量和类变量)

    Java单例模式 背景知识:Static关键字. 在对于定义类的变量,分为两种,是否具有static修饰的变量: 没有static修饰的变量,通过类的实例化(对象)引用,改变量称为实例变量: 使用st ...

  2. Java 单例模式探讨

    以下是我再次研究单例(Java 单例模式缺点)时在网上收集的资料,相信你们看完就对单例完全掌握了 Java单例模式应该是看起来以及用起来简单的一种设计模式,但是就实现方式以及原理来说,也并不浅显哦. ...

  3. Java单例模式优化写法

    转载自 http://blog.csdn.net/diweikang/article/details/51354982 Java单例模式优化写法 方法一:推荐 [java] view plain co ...

  4. Java单例模式的几种实现方式

    Java单例模式的几种实现方式 在Java 中,单例类只能有一个实例,必须创建自己的唯一实例,单例类必须给所有其他对象提供这一实例.Java 单例模式有很多种实现方式,在这里给大家介绍单例模式其中的几 ...

  5. Java 单例模式:懒加载(延迟加载)和即时加载

    Java 单例模式:懒加载(延迟加载)和即时加载 引言 在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制(或懒加载.延时加载),也就是说只有当使用到这个实例的时候才会创 ...

  6. Java单例模式:为什么我强烈推荐你用枚举来实现单例模式

    写在前面--原作的这篇文章真的写的非常的简洁,逻辑清晰,将Java单例模式的各种写法写的非常清楚,并介绍了用枚举实现单例的最佳实践. 单例模式简介 单例模式是 Java 中最简单,也是最基础,最常用的 ...

  7. java单例模式 三种_三种java单例模式概述

    在java语言的应用程序中,一个类Class只有一个实例存在,这是由java单例模式实现的.Java单例模式是一种常用的软件设计模式,java单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种.下 ...

  8. 什么是java单例模式?

    关于java单例模式的文章早已是非常多了,本文是对我个人过往学习java,理解及应用java单例模式的一个总结.此文内容涉及java单例模式的基本概念,以及什单例模式的优缺点,希望对大家有所帮助. 什 ...

  9. 比心app源码,Java 单例模式

    比心app源码,Java 单例模式实现的相关代码 概述:单例模式是指在内存中永远只有一个类的实例. 有利于节约内存和保证共享计算的结果正确,方便管理. 单例模式的形式 饿汉式单例:在获取单例对象之前对 ...

  10. Java单例模式及开发应用场景

    一.Java单例模式是什么? 所谓单例,指的就是单实例,有且仅有一个类实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例. 二.为什么要用单例模式? 单例有其独有的使用场景,一般是对于那些业 ...

最新文章

  1. 作为候选人,你需要问些什么?你需要查些什么?这些关乎你的利益和未来!
  2. 【每日一题】剑指 Offer 10- I. 斐波那契数列
  3. 无法解决 equal to 操作中 SQL_Latin1_General_CP1_CI_AS 和 Chinese_PRC_CI_AS 之间的排序规则冲突。...
  4. 练习一下linux中的list函数。
  5. ##安装MySql数据库并解决如果安装出错卸载的注意事项
  6. 关于intel 32 hex文件格式以及hex2rom.sed
  7. memcached 相关
  8. P2260 [清华集训2012]模积和,P2834 能力测验(二维除法分块)
  9. Eclipse中使用Checkstyle,checkstyle插件检查java代码的自定义配置文件:
  10. python函数控制词典_Python 基础之集合相关操作与函数和字典相关函数
  11. 【ACL2020】最新效果显著的关系抽取框架了解一下?
  12. [linux] C语言Linux系统编程-做成守护进程
  13. Irrlicht引擎例子说明及中文链接
  14. matlab 转移矩阵,matlab转移矩阵
  15. 计算机无线网络设备有哪些,电脑无线上网设备有哪几种
  16. DataCastle员工离职预测数据竞赛个人总结
  17. 计算机count是什么函数,2010年职称计算机考试:计数函数COUNT
  18. 【SSM框架】【怠惰致错】Invalid bound statement (not found)
  19. 吉西他滨纳米载药细胞膜囊泡|红细胞囊泡包载的纳米药物(齐岳试剂)
  20. android逆向快手,[原创] 快手签名-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com...

热门文章

  1. 【算法】平衡二叉树 Avl 树
  2. 【elasticsearch】The number of object passed must be even but was [1]
  3. 高并发中计数器的实现方式有哪些?
  4. 【Java】JDK8新特性之方法引用
  5. 【Kafka】Kafka No serviceName defined in either JAAS or Kafka config
  6. spark学习-76-目标:如何成为大数据Spark高手
  7. 95-170-044-源码-Time-flink时间Processing Time源码分析
  8. 95-190-740-源码-WindowFunction-窗口流侧的窗口函数(外部函数)
  9. 95-270-019-源码-指标监测-常用监控指标
  10. 【Hbase】报错org.apache.hadoop.hbase.RegionTooBusyException