首先明确一个问题,在某些情况下,有些对象,我们只需要一个就可以了。比如,一台计算机连接到多个打印机上,但是这个计算机上的打印程序只能有一个,因为可能会有多个打印作业同时输出到打印机中,这里就可以通过单例模式来避免这个问题。

  简单来说,单例模式的作用就是保证在应用程序的生命周期中,任何一个时刻,单例类的实例只有一个(当然也可以不存在)。

单例模式的类图:

从上面的类图中可以看出,在单例类中有一个私有的构造函数 Singleton ,

有一个声明为public的 GetInstance()的方法返回一个Singleton实例。

通过上面的类图不难看出单例模式的特点,从而也可以给出

单例模式的定义:单例模式保证一个类仅有一个实例,同时这个类还必须提供一个访问该类的全局访问点。

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

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

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

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

 怎么解决线程安全问题?

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

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

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

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

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

但是经过分析发现这个方法是不行的!

原因是:
  1、线程A和线程B同时进入//1的位置。这时instance是为空的。
  2、线程A进入synchronized块,创建实例,线程B等待。
  3、线程A返回,线程B继续进入synchronized块,创建实例。。。
  4、这时已经有两个实例创建了。

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

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

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

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

 1 public static Singleton getInstance() {
 2         if (instance == null) {
 3             synchronized (Singleton.class) { //1
 4                 Singleton temp = instance;  //2
 5                 if (temp == null) {
 6                     synchronized (Singleton.class) { //3
 7                         temp = new Singleton();  //4
 8                     }
 9                     instance = temp;     //5
10                 }
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 线程安全 优点: 1、线程安全 缺点:
 3  * 1、非懒加载(饿汉式),如果构造的单例很大,构造完又迟迟不使用,会导致资源浪费。
 4  *
 5  * @author Wahson Leung
 6  * @version 2013-4-20
 7  *
 8  */
 9 public class HungrySingleton {
10     /** 单例变量 ,static的,在类加载时进行初始化一次,保证线程安全 */
11     private static HungrySingleton instance = new HungrySingleton();
12
13     /**
14      * 私有化的构造方法,保证外部的类不能通过构造器来实例化。
15      */
16     private HungrySingleton() {
17     }
18
19     /**
20      * 获取单例对象实例
21      *
22      * @return 单例对象
23      */
24     public static HungrySingleton getInstance() {
25         return instance;
26     }
27 }

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

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

  方法四:使用内部类。

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

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

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

转载于:https://www.cnblogs.com/wahsonleung/archive/2013/04/20/3031908.html

设计模式-单例模式(Singleton)相关推荐

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

    二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元 ...

  2. 常见设计模式—单例模式(Singleton)

    前言 好久没写东西了,但是想着无论什么事还是要坚持自己初心要坚持的东西.写东西不能断! 对于常用的23种设计模式,这里笔者会根据自己学习和出现频率.重要程度进行学习记录吧.并且每种设计模式可能会根据暂 ...

  3. C++设计模式--单例模式(Singleton)及单例通用模板

    概述 C++中的单例模式应该是设计模式中最简单的了,在编码中常见到.那么,简单的总结下 C++中的单例模式写法,以及根据单例模式扩展后的一些写法,最后还有单例的通用模板,可用于快捷创建一个单例类. 单 ...

  4. 设计模式 -- 单例模式(Singleton)

    写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------- 主要内容包括: 初始单例 ...

  5. 设计模式——单例模式(Singleton)

    保证一个类仅有一个实例,并提供一个访问它的全局访问点.--DP UML类图 模式说明 个人认为单例模式是所有设计模式中最为简单的一个模式,因为实现这个模式仅需一个类,而不像其他模式需要若干个类.这个模 ...

  6. Python 设计模式: 单例模式(singleton pattern)

    2019独角兽企业重金招聘Python工程师标准>>> 如果想在整个程序的运行过程中,某个类只有一个实例的话,可以通过单例模式来实现. 在 Python 中实现单例模式有很多种方式, ...

  7. 解读设计模式----单例模式(Singleton Pattern)

    单例模式可以保证一个类有且只有一个实例,并提供一个访问它的全局访问点.在程序设计中,有很多情况需要确保一个类只能有一个实例.从这句话可以看出,Singleton模式的核心:如何控制用户使用new对一个 ...

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

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

  9. 设计模式---单例模式Singleton

    /*** 饿汉式* 类加载到内容后,就实例化一个实例,* JVM保证线程安全: JVM保证每一个Class只会load到内存一次,static关键字是在Class load到内存之后马上就行初始化,也 ...

  10. 趣谈设计模式 | 单例模式(Singleton) :独一无二的对象

    文章目录 单例模式 饿汉模式 懒汉模式 懒汉和饿汉的区别 单例模式 一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全 局访问点,该实例被所有程序模块共享. ...

最新文章

  1. Datawhale来厦大啦!
  2. 5G、物联网和AI结合的究极形态是什么?一文看尽智能连接在5大领域的12个典型案例!...
  3. PHP文件上传,下载,Sql工具类!
  4. C语言quick sort快速排序的算法(附完整源码)
  5. 邮件服务器“单点登录”功能
  6. 简单谈谈js中的MVC
  7. 程序设计中的数学思维函数总结(代码以C#为例)
  8. opencv 凸包讲解和绘制
  9. excel冻结行和列_怎样锁定Excel表格中的某一行或某一列
  10. win7计算机图标删除,如何彻底删除Win7右下角操作中心的小白旗图标
  11. 微星B450mMortar迫击炮+AMD速龙3000GE安装Windows7 SP1并使用UEFI+Nvme启动
  12. 用phpcms切换中英文网页的方法(不用解析二级域名)、phpcms完成pc和手机端切换(同一域名)...
  13. TIA博途如何从DB数据块生成源文件或者源文件生成DB数据块?
  14. 简述对css盒子模型的理解_css 盒子模型理解
  15. 八年双11背后:新技术新能源成为新基础
  16. 视频教程-ThreeJS 3D高级教程-JavaScript
  17. 饼状图(PieChart)与柱形图(BarChart)的使用
  18. Java——课设——图书管理系统实现 2.0
  19. win10怎么把快捷方式添加到开始菜单
  20. Win10安装fbprophet实录

热门文章

  1. jmeter入门学习,第二篇jmeter采样器sample
  2. 为何Emacs和Vim被称为两大神器
  3. kaminari分页插件样式
  4. Linux 下安装ClamAV查毒软件
  5. git 远程仓库和本地仓库建立连接
  6. 提高团队协作效率就靠它们了!
  7. java获取docx_java使用poi读取doc和docx文件的实现示例
  8. 图片格式与设计那点事儿
  9. 球定位实景导航 iOS实用应用周排行
  10. java中使用MD5验证文件的完整性