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

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

/*** 基础的单例模式,Lazy模式,非线程安全* 优点:lazy,初次使用时实例化单例,避免资源浪费* 缺点:1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题* 2、非线程安全。多线程下可能会有多个实例被初始化。* * @author laichendong* @since 2011-12-5*/
public class SingletonOne {/** 单例实例变量 */private static SingletonOne instance = null;/*** 私有化的构造方法,保证外部的类不能通过构造器来实例化。
17*/private SingletonOne() {}/*** 获取单例对象实例* * @return 单例对象
26*/public static SingletonOne getInstance() {if (instance == null) { // 1instance = new SingletonOne(); // 2}return instance;}}

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

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

 怎么解决线程安全问题?

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

使用同步方法的单例
/*** copyright © sf-express Inc*/
package com.something.singleton;/*** 同步方法 的单例模式,Lazy模式,线程安全* 优点:* 1、lazy,初次使用时实例化单例,避免资源浪费* 2、线程安全* 缺点:* 1、lazy,如果实例初始化非常耗时,初始使用时,可能造成性能问题* 2、每次调用getInstance()都要获得同步锁,性能消耗。* * @author laichendong* @since 2011-12-5*/
public class SingletonTwo {/** 单例实例变量 */private static SingletonTwo instance = null;/*** 私有化的构造方法,保证外部的类不能通过构造器来实例化。
*/private SingletonTwo() {}/*** 获取单例对象实例* 同步方法,实现线程互斥访问,保证线程安全。* * @return 单例对象
*/public static synchronized SingletonTwo getInstance() {if (instance == null) { // 1instance = new SingletonTwo(); // 2}return instance;}}

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

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

public static SingletonThree getInstance() {if (instance == null) { // 1synchronized (SingletonThree.class) {instance = new SingletonThree(); // 2}}return instance;}

这个方法可行么?分析一下发现是不行的!


  1、线程A和线程B同时进入//1的位置。这时instance是为空的。


  2、线程A进入synchronized块,创建实例,线程B等待。


  3、线程A返回,线程B继续进入synchronized块,创建实例。。。


  4、这时已经有两个实例创建了。 

  为了解决这个问题。我们需要在//2的之前,再加上一次检查instance是否被实例化。(双重检查加

锁)接下来,代码变成了这样:

public static SingletonThree getInstance() {if (instance == null) { // 1synchronized (SingletonThree.class) {if (instance == null) { instance = new SingletonThree(); // 2}}}return instance;}

这样,当线程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的初始化,再返回。 

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

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

解释一下执行步骤。
  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变量。

/*** 预先初始化static变量 的单例模式  非Lazy  线程安全* 优点:* 1、线程安全 * 缺点:* 1、非懒加载,如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。* * @author laichendong* @since 2011-12-5*/
public class SingletonFour {/** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */private static SingletonFour instance = new SingletonFour();/*** 私有化的构造方法,保证外部的类不能通过构造器来实例化。*/private SingletonFour() {}/*** 获取单例对象实例* * @return 单例对象*/public static SingletonFour getInstance() {return instance;}}

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

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

  方法四:使用内部类。

/*** 基于内部类的单例模式  Lazy  线程安全* 优点:* 1、线程安全* 2、lazy* 缺点:* 1、待发现* * @author laichendong* @since 2011-12-5*/
public class SingletonFive {/*** 内部类,用于实现lzay机制
*/private static class SingletonHolder{/** 单例变量  */private static SingletonFive instance = new SingletonFive();}/*** 私有化的构造方法,保证外部的类不能通过构造器来实例化。
*/private SingletonFive() {}/*** 获取单例对象实例* * @return 单例对象
*/public static SingletonFive getInstance() {return SingletonHolder.instance;}}

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

 

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

java设计模式---单例模式相关推荐

  1. Java 设计模式 - 单例模式

    Java 设计模式 - 单例模式 作者: 霍英俊 [huo920@live.com] 文章目录 Java 设计模式 - 单例模式 单例设计模式介绍 单例设计模式八种方式 饿汉式 - 静态常量 饿汉式( ...

  2. Java设计模式——单例模式

    单例模式 1.什么是单例模式. 2.单例设计的几种实现方式. 2.1.懒汉式 2.2.饿汉式 2.3.登记式 3.总结 4.建议 1.什么是单例模式. 确保某一个类只有一个实例,并且提供一个全局访问点 ...

  3. Java设计模式——单例模式的七种写法

    单例模式(Singleton) 单例模式(Singleton)是一种常用的设计模式.在Java应用中,单例模式能保证在一个JVM中,该对象只有一个实例存在.这样的模式有几个好处: 1.某些类创建比较频 ...

  4. JAVA设计模式--单例模式

    Singleton是一种创建型模式,指某个类采用Singleton模式,则在这个类被创建后,只可能产生一个实例供外部访问,并且提供一个全局的访问点. 核心知识点如下: (1) 将采用单例设计模式的类的 ...

  5. 我的Java设计模式-单例模式

    就算不懂设计模式的兄弟姐妹们,想必也听说过单例模式,并且在项目中也会用上.但是,真正理解和熟悉单例模式的人有几个呢?接下来我们一起来学习设计模式中最简单的模式之一--单例模式 一.为什么叫单例模式? ...

  6. JAVA设计模式 - 单例模式

    单例模式(Singleton)是软件设计中一种比较常见的 , 相对简单的设计模式 . 1 . 单例模式的定义 所谓单例 , 指的就是单示例 , 即某个类的实现对象有且仅能有一个 , 并提供对外调用的方 ...

  7. java设计模式—单例模式

    (一)单例模式  java中一共有23种设计模式 : 是开发人员根据不同的代码场景总结出来的不同的实现方法, 归纳为23种代码的设计方法, 单例模式就是其中的一种. 单例模式 : 在整个系统中,一个类 ...

  8. 【文末抽书】Java设计模式--单例模式

    来源 :投稿 | 作者 : gyl-coder|原文:阅读原文 在介绍单例模式之前,我们先了解一下,什么是设计模式? 设计模式(Design Pattern):是一套被反复使用,多数人知晓的,经过分类 ...

  9. JAVA设计模式-单例模式(Singleton)线程安全与效率

    一,前言 单例模式详细大家都已经非常熟悉了,在文章单例模式的八种写法比较中,对单例模式的概念以及使用场景都做了很不错的说明.请在阅读本文之前,阅读一下这篇文章,因为本文就是按照这篇文章中的八种单例模式 ...

最新文章

  1. 常见Jvm面试题总结及答案整理 120道(持续更新)
  2. 表单美化-原生javascript和jQuery下拉列表(兼容IE6)
  3. 我是不会运行你的代码吗?不,我是不会导入自己的数据!
  4. 修改centos6.3启动级别以及启动级别配置错误的修改问题
  5. 我的conky 配置(拆分版)
  6. igbt原理动画演示视频_简单易懂的IGBT工作原理分析
  7. 前端弹出对话框 js实现 ajax交互
  8. caffe调用之前的权重和接着断点继续训练
  9. 口碑最好的国产蓝牙耳机,2021国产最好用的蓝牙耳机
  10. NVIDIA GeFprce GTX 1080 Ti NVIDIA图形驱动程序版本466.77下载和安装
  11. 既有内网又有外网的网络如何设置路由器模式
  12. 流光溢彩PCTV[WLED]
  13. CSP 2021 复赛游记
  14. 数据库系统概论(第四版)习题解答
  15. 2021第五届蓝帽杯初赛部分题目wp
  16. unity-调用动态库so-android篇
  17. [ECCV 2020] Distribution-balanced loss for multi-label classification in long-tailed datasets
  18. Origin软件科技绘图分析功能使用介绍,Origin软件中文版下载安装
  19. 搭建web邮箱extmail
  20. COMS原理及门电路设计

热门文章

  1. bzoj千题计划143:bzoj1935: [Shoi2007]Tree 园丁的烦恼
  2. 009-对象—— 构造方法__construct析构方法__destruct使用方法 PHP重写与重载
  3. 单点登录 - 修改CAS服务器的一些配置( 陆续添加)
  4. 简约设计中的规律—色彩(二)
  5. log4j在javaWeb项目中的使用
  6. The Power of Ten – Rules for Developing Safety Critical Code
  7. openwrt使用3G上网卡
  8. 域用户不能使用远程桌面登录
  9. 关于maven工程中一直报和依赖包json-lib-2.4-jdk15.jar相关错误的问题解决方法
  10. 基础知识《二》java的基本类型