单例模式

1 简介

1.1 什么是单例模式?

Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.
单例模式,一种创建型设计模式。它需要确保只有一个同类对象存在,并为任何其他代码提供对它的单一访问点。

不妨先看一段简单的代码示例:

public class SingletonDemo {
  //私有的静态实例,提前准备好
  private static SingletonDemo instance = new SingletonDemo();
  //公开方法提供了私有实例的唯一获取点
  public static SingletonDemo getSingleTonDemo(){
    return instance;
  }
  //私有的构造方法确保了该对象实例不能在其他代码中new出来。
  private SingletonDemo() {}
}
1.2 单例模式的特点

从单例模式的定义可以看出单例模式的两个重要特点:1. 有且仅有一个的唯一实例;2. 对外的公开访问方法

结合定义和代码实现,可以看出代码实现其实是紧扣定义的:

  • 私有的构造方法,确保了该类不能被其他代码通过构造方法制造出来,从而产生多个实例。
  • 私有的静态实例,确保了实例的唯一性。
  • 公开的访问方法,确保了其他系统可以使用唯一的实例。
1.3 单例模式的优缺点分析

让我们单从软件设计的角度分析下单例模式的优缺点:

  • 优点
    1. 单例模式产生的唯一对象可以做到最大程度上的对象复用,从而减少内存的不必要浪费。
    2. 因为类控制了实例化过程,所以类本身可以更灵活地控制实例化过程。
  • 缺点
    1. 单例模式违背了单一职责原则,类中耦合了实例化过程,这必然导致类难以测试、难以维护。

2 使用场景

2.1 类比真实世界

分析单例模式离不开类比现实世界的场景,任何模式的背后都是现实世界的场景的映射,所以从现实世界的场景出发更能加深记忆和理解。

设想一下,在我们的办公室,你需要打印一个文件,这个时候你是申请买一台打印机还是使用那台共用的打印机呢?答案显然是使用那台公用的打印机,因为不同于小物件,新买一台打印机的代价实在是太大了,我们使用公用的打印机可以节省的成本不是一点点。

这样的例子是非常多的,比如你不会造一个泳池而是去公用的泳池游泳,你不会重新聘请一个物业而是去物业中心去缴纳费用。

当面对这些庞大且需要公用的事物时,我们希望做到的就是在一定程度上确保单个唯一实例,如此也就可以最大可能的降低成本而不是铺张浪费。

2.2 程序中的单例模式

程序世界更是不乏这样的例子,比如JDBC连接池就是单例模式,Windows的任务管理就是单例模式,多线程编程的线程池也是单例模式。

3 代码实现

当掌握了单例模式的概念核心之后,显然单例模式有很多种代码实现。从简单的实现到考虑到线程安全和性能,实现方式的背后其实是更深层次的思考和逻辑。

3.1 饿汉式

之所以称为饿汉是因为实例直接是被new出来的,无论调用与否。就像一个饥饿的人,早早的把食物(实例)准备好了。当调用getInstance()方法的时候直接将已经准备好的实例提供即可。

public class HungrySingleton {
  private HungrySingleton() {}
  private static final HungrySingleton instance = new HungrySingleton();
  public static HungrySingleton getInstance() {
    return instance;
  }
}
  • 优点
    1. 写法简单
    2. 类加载过程中就已经实例化,避免了线程同步问题。
  • 缺点
    1. 过早的实例化没有达到懒加载的效果,如果没有调用者,显然是浪费内存的。
3.2 懒汉式

之所以称为懒汉式,因为实例是在获取的时候才判断有没有,如果没有就new出来一个实例,如果有就返回已有的,像一个懒人等着别人催他一样。

public class LazySingleton {
  private LazySingleton () {}
  private static LazySingleton instance;
  public static LazySingleton getInstance() {
    if (instance == null) {
      return new LazySingleton();
    }
    return instance;
  }
}
  • 优点
    1. 相比较饿汉式,显然懒汉式做到了懒加载,资源得到了节省。
  • 缺点
    1. 但是多线程下,条件判断部分是线程不安全的。当存在多个线程就有可能产生多个实例。
3.3 线程安全的懒汉式

线程安全的懒汉式其实就是在获取实例的方法上加入synchronzed修饰符,以确保线程安全。

//同步方法,方法锁
public class SynchronizedLazySingleton {
  private SynchronizedLazySingleton () {}
  private static SynchronizedLazySingleton instance;
  public static synchronized SynchronizedLazySingleton getInstance() {
    if (instance == null) {
      return new SynchronizedLazySingleton();
    }
    return instance;
  }
}
}

//同步代码块,对象锁
public class SynchronziedBlockLazySingleton {
  private SynchronziedBlockLazySingleton () {}
  private static SynchronziedBlockLazySingleton instance;
  public static  SynchronziedBlockLazySingleton getInstance() {
    if (instance == null) {
      synchronized (SynchronziedBlockLazySingleton.getInstance()) {
        return new SynchronziedBlockLazySingleton();
      }
    }
    return instance;
  }
}
  • 优点
    1. 既做到了懒汉式的懒加载又做了线程安全。
  • 缺点
    1. syncronized关键字本身对于线程安全的性能是不友好的。
3.4 双重检查

双重检查(Double check)是在对象锁的基础上再次升级,实例使用关键字volatile修饰确保唯一性。获取实例方法中双重判断是否已经存在。

public class DoubleCheckSingleton {
  private DoubleCheckSingleton () {}
  private volatile static DoubleCheckSingleton instance;
  public static  DoubleCheckSingleton getInstance() {
    if (instance == null) {
      synchronized (DoubleCheckSingleton.getInstance()) {
        if (instance == null) {
          return new DoubleCheckSingleton();
        }
      }
    }
    return instance;
  }
}
  • 优点
    1. 既做到了懒加载又做了线程安全并且效率得到了提高。
  • 缺点
    1. 程序员记不住怎么写。
3.5 静态内部类

静态内部类在外部类装载过程中不会实例话,只有在调用getInstance方法时才会实例话,做到了懒加载。而类的静态属性只有在初始化的时候才会执行,从而确保了线程安全。

public class StaticInerClassSingleton {
  private StaticInerClassSingleton() {}
  private static class InnerSingleton {
    private static final StaticInerClassSingleton INSTANCE = new StaticInerClassSingleton();
  }
  public static StaticInerClassSingleton getInstance() {
    return InnerSingleton.INSTANCE;
  }
}
  • 优点
    1. 既做到了懒加载又做了线程安全还没有性能担忧。
  • 缺点
    1. 程序员记不住怎么写。
3.6 枚举类

枚举类是JDK1.5才出现的,那之前的程序员面对反射攻击和序列化问题是怎么解决的呢?其实就是像Enum源码那样解决的,只是现在可以用enum可以使我们代码量变的极其简洁了。

具体分析参考:https://www.cnblogs.com/chiclee/p/9097772.html

public enum EnumSingleton {
  INSTANCE;
}
  • 优点
    1. 线程安全,懒加载,没有性能问题。
    2. 简洁。
  • 缺点
    1. 程序员不知道为什么这样写。

4 总结

单例模式的目的是确保系统中的庞大公用对象只存在唯一的实例从而做到最大程度的复用以提升系统性能比

本文只聚焦于单例模式本身的含义,所以上述的实现方式没有过多的解释,然而具体每一种单例模式的实现都可以深究。当然我们不能忽略实际编码过程中的性能问题安全问题以及简介性,为此我们分别使用了以下方式并不断优化:

  1. 私有构造器 + 静态实例
  2. 私有构造器 + 静态实例存在判断
  3. 私有构造器 + 静态实例存在判断 + synchronzied关键字
  4. 私有构造器 + 静态实例存在判断 + synchronzied关键字 + volatile关键字 + double check
  5. 私有构造器 + 静态内部类
  6. 枚举类型的高度封装,浑然天成

我们从非线程安全非懒加载到线程安全和懒加载以及性能提升直至最后的枚举类在所有基础上还能防止私有构造器攻击以及解决序列化后对象不相等的问题。

其实static、synchronzied、volatile关键字以及枚举类型源码、静态内部类、如何通过反射攻破私有构造器、序列化问题这些概念单独一个都够讨论半天的,所以篇幅受限我们还是聚焦在单例模式的概念本身,其余概念和问题我们可以自行补齐。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,326评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,228评论 1 304
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,979评论 0 252
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,489评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,894评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,900评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,075评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,803评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,565评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,778评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,255评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,582评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,254评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,151评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,952评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,035评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,839评论 2 277

推荐阅读更多精彩内容