1、模式简介

  单例模式在代码中是非常常用的,如线程池、数据库连接、注册表、共享资源、一些非常消耗资源的组件,等等。

单例模式主要解决如下问题:

  • 确保一个特殊类的实例是独一无二的;
  • 确保这个类的实例非常容易访问(提供了这个类的一个全局访问指针);

以下情况下可以使用单例模式:

  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时;
  • 当要创建的对象非常耗费资源,而且全局用到了很多次时;
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

2、实现方法

2.1、整体思路

单例模式的整体思路如下:

  • 在单例类中创建一个私有的本类对象;
  • 将单例类的构造方法私有化,即用private修饰;
  • 创建一个静态的方法getInstance(),返回本类类型的变量,在这个方法中返回单例。

  根据这种思路,单例模式分为饿汉式单例和懒汉式单例。

2.2、饿汉式

  饿汉式单例即在声明实例对象的时候就直接创建对象,在需要引用的时候直接引用。

  饿汉式单例的简单代码如下:

  1. /**
  2. * 饿汉式单例的单例类
  3. */
  4. public class HungerSingleton {
  5. // 在声明单例变量的时候直接初始化
  6. private static HungerSingleton instance = new HungerSingleton();
  7.  
  8. // 私有化构造方法,避免外界直接访问
  9. private HungerSingleton() {
  10. System.out.println("我是饿汉式单例的唯一实例!");
  11. }
  12.  
  13. // 公共的静态方法,用于外界调用该方法来创建唯一的对象
  14. public static HungerSingleton getInstance() {
  15. return instance;
  16. }
  17.  
  18. public void introduce() {
  19. System.out.println("我是introduce()方法,我被调用了!" + Thread.currentThread().getName());
  20. }
  21. }

  测试代码:

  1. public class Test {
  2. public static void main(String[] args) {
  3. new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. HungerSingleton.getInstance().introduce();
  7. }
  8. }).start();
  9.  
  10. new Thread(new Runnable() {
  11. @Override
  12. public void run() {
  13. HungerSingleton.getInstance().introduce();
  14. }
  15. }).start();
  16.  
  17. new Thread(new Runnable() {
  18. @Override
  19. public void run() {
  20. HungerSingleton.getInstance().introduce();
  21. }
  22. }).start();
  23.  
  24. new Thread(new Runnable() {
  25. @Override
  26. public void run() {
  27. HungerSingleton.getInstance().introduce();
  28. }
  29. }).start();
  30.  
  31. new Thread(new Runnable() {
  32. @Override
  33. public void run() {
  34. HungerSingleton.getInstance().introduce();
  35. }
  36. }).start();
  37. }
  38. }

  运行结果:

  从代码和运行结果来看,我们在五个线程中分别调用了HungerSingleton.getInstance()方法来创建对象,但HungerSingleton类的构造方法只被调用了一次,也就是说,五次调用只创建了一个对象。可以看出,饿汉式单例不仅保证了只创建一个对象,也保证了线程安全。

  综上,饿汉式单例的优点是线程安全的,即无论在多少个线程中调用这个对象,调用的都是事先声明好的;缺点是声明出来的对象可能很占用系统资源,会在调用之前影响系统的工作状态。

2.3、懒汉式

  懒汉式单例即在声明的时候不创建对象,在需要引用的时候先创建对象,然后引用。

  懒汉式单例的简单代码如下:

  1. public class LazySingleton {
  2. // 声明变量,但不初始化
  3. private static LazySingleton instance;
  4.  
  5. // 私有化构造方法,避免外界直接访问
  6. private LazySingleton() {
  7. System.out.println("我是懒汉式单例类的对象,我是唯一的!");
  8. }
  9.  
  10. // 公共的静态方法,用于外界调用该方法来创建唯一的对象
  11. public static LazySingleton getInstance() {
  12. // 在调用对象的时候判断对象是否存在,如果存在则直接使用,否则才创建对象
  13. if (instance == null) {
  14. instance = new LazySingleton();
  15. }
  16. return instance;
  17. }
  18.  
  19. public void introduce() {
  20. System.out.println("我是introduce()方法,我被调用了!" + Thread.currentThread().getName());
  21. }
  22. }

  测试代码和饿汉式单例的测试代码基本相同,也是五个线程同时访问。运行结果如下:

  从代码和运行结果来看,当有多个线程同时访问这个单例对象的时候,就有可能创建出多个对象,这不符合单例模式的初衷。为了解决这个方法,我们需要对懒汉式单例进行一定的改进,方法是为创建单例的代码加一层双重检查锁。具体代码如下:

  1. // 公共的静态方法,用于外界调用该方法来创建唯一的对象
  2. public static LazySingleton getInstance() {
  3. // 在调用对象的时候判断对象是否存在,如果存在则直接使用,否则才创建对象
  4. if (instance == null) {
  5. synchronized (LazySingleton.class) {
  6. if (instance == null) {
  7. instance = new LazySingleton();
  8. }
  9. }
  10. }
  11. return instance;
  12. }

  再次运行,运行结果如下:

  综上,懒汉式单例的优点是在没有调用单例对象之前不会影响系统的工作状态;缺点是线程不安全,即如果在多个线程中同时访问这个单例对象,很可能会创建出多个对象,但这种缺点是可以改进的,方法就是加双重检查锁。

3、模式优缺点

单例模式有以下优点:

  • 使用单例模式可以严格的控制用户怎样以及如何访问它;
  • 节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式可以提高系统的性能;
  • 允许可变数目的实例。

单例模式有以下缺点:

  • 单例模式的扩展有很大的困难;
  • 单例类职责过重,在一定程度上违背了“单一职责原则”;
  • 滥用单例模式可能产生一些负面问题,如一个对象的访问过多可能引起对象溢出;如果实例化的对象长时间不被使用,系统会默认为是垃圾而回收,导致对象状态的丢失。

4、多实例单例

  前面说过,单例可以控制生成的对象的个数或不同种类的对象。例如,一个单例类可能会有多个子类,这些子类都是对父类的扩展,每个子类都有其独特的作用,那么我们可能就会控制每个子类都可以有一个单例对象,这时就用到了多实例单例。

  其实想想也不奇怪,我们既然能控制一个类只能生成一个对象,那么控制这个类生成固定个数个对象自然也不成问题。要解决这个问题,我们需要用到集合类,Map、List都可以,每当需要创建一个对象的时候,我们就遍历这个集合,判断这个对象在集合中有没有单例,如果有,则直接调用集合中的对象;如果没有,才可以创建一个对象然后存到集合中。

  当然,这种多实例单例的方法也不仅用在每个类只能创建一个实例的情况,也可以用在一个类可以创建多个相同的实例的情况,其解决方法依然是使用集合,具体方法如上,就不多说了。

  最后贴出单例模式的GitHub地址:【GitHub - Singleton】

【设计模式 - 2】之单例模式(Singleton)的更多相关文章

  1. 【白话设计模式四】单例模式(Singleton)

    转自:https://my.oschina.net/xianggao/blog/616385 0 系列目录 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factor ...

  2. IOS设计模式浅析之单例模式(Singleton)

    说在前面 进入正式的设计模式交流之前,扯点闲话.我们在项目开发的过程中,经常会不经意的使用一些常见的设计模式,如单例模式.工厂方法模式.观察者模式等,以前做.NET开发的时候,认真拜读了一下程杰老师的 ...

  3. 设计模式系列之单例模式(Singleton Pattern)——确保对象的唯一性

    模式概述 模式定义 模式结构图 饿汉式单例与懒汉式单例 饿汉式单例 懒汉式单例 模式应用 模式在JDK中的应用 模式在开源项目中的应用 模式总结 主要优点 适用场景 说明:设计模式系列文章是读刘伟所著 ...

  4. Net设计模式实例之单例模式( Singleton Pattern)

    一.单例模式简介(Brief Introduction) 单例模式(Singleton Pattern),保证一个类只有一个实例,并提供一个访问它的全局访问点.单例模式因为Singleton封装它的唯 ...

  5. JavaScript设计模式 Item 6 --单例模式Singleton

    单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池.全局缓存.浏览器的window对象.在js开发中,单例模式的 ...

  6. 设计模式系列之单例模式(Singleton Pattern)

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式.这种模式涉及到一个单一的类,该类负责创建自己的对象 ...

  7. Python设计模式 - 创建型 - 单例模式(Singleton) - 十种

    对于很多开发人员来说,单例模式算是比较简单常用.也是最早接触的设计模式了,仔细研究起来单例模式似乎又不像看起来那么简单.我们知道单例模式适用于提供全局唯一访问点,频繁需要创建及销毁对象等场合,的确方便 ...

  8. 我的设计模式学习笔记------>单例模式(Singleton)

    一.前言 有些时候,允许自由创建某个类的实例是没有意义,还可能造成系统性能下降(因为创建对象所带来的系统开销问题).例如整个Windows系统只有一个窗口管理器,只有一个回收站等.在Java EE应用 ...

  9. 设计模式之三:单例模式singleton

    单例设计模式确切的说就是一个类只有一个实例,有一个全局的接口来访问这个实例.当第一次载入的时候,它通常使用延时加载的方法创建单一实例. 提示:苹果大量的使用了这种方法.例子:[NSUserDefaul ...

  10. 《JAVA设计模式》之单例模式(Singleton)

    在阎宏博士的<JAVA与模式>一书中开头是这样描述单例模式的: 作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例.这个类称为单例类. 单例模式的 ...

随机推荐

  1. 不为人知的Locked

    在属性面板上,有一个Locked属性,什么时候添加上去的,真是没有注意到,它的说明为:确定是否可以移动控件或调整控件的大小,默认是为false的,设置为true以后,当前选中的控件会在左上角出现一个锁 ...

  2. session与cookie的区别,有哪些不同之处

    session与cookie的区别,根据自己的理解总结如下: (1)cookie是一种客户端的状态管理技术,将状态写在 浏览器端,而session是一种服务器端的状态管理技术,将 状态写在web服务器 ...

  3. Day16 DOM &jQuery

    一.本节主要内容 JavaScript 正则表达式 字符串操作的三个方法 DOM(知道就行,一般使用jQuery) 查找: 直接查找: document.getElementById 根据ID获取一个 ...

  4. decimall类型数据

    同样是decimal(18,5)   和 decimal(18,4)  在VB中经过几次转化过后,数据就有可能改变. 遇到的情况 decimal(18,5)到  decimal(18,4)转换过程中数 ...

  5. 扩展原生js的一些方法

    扩展原生js的Array类 Array.prototype.add = function(item){ this.push(item); } Array.prototype.addRange = fu ...

  6. 初识Vim

    在Windows系统安装Vim后桌面上会添加gVim.gVim Easy.gVim Read-only 三个快捷方式. gVim 指向主程序,gVim Easy.gVim Read-only 也是,但 ...

  7. Hadoop 学习笔记 (十) MapReduce实现排序 全局变量

    一些疑问:1 全排序的话,最后的应该sortJob.setNumReduceTasks(1);2 如果多个reduce task都去修改 一个静态的 IntWritable ,IntWritable会 ...

  8. BZOJ 1026 windy数

    Description windy定义了一种windy数.不含前导零且相邻两个数字之差至少为2的正整数被称为windy数. windy想知道,在A和B之间,包括A和B,总共有多少个windy数? In ...

  9. NSUserDefaults(数据存储)

    NSUserDefaults(数据存储)   NSUserDefaults适合存储轻量级的本地数据,比如要保存一个登陆界面的数据,用户名.密码之类的,个人觉得使用NSUserDefaults是首选.下 ...

  10. C语言嵌入式系统编程修炼之五:键盘操作

    处理功能键 功能键的问题在于,用户界面并非固定的,用户功能键的选择将使屏幕画面处于不同的显示状态下.例如,主画面如图1:图1 主画面 当用户在设置XX上按下Enter键之后,画面就切换到了设置XX的界 ...