单例模式有哪些实现方法

(单例是创建型模式的一种。创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。其中,单例模式用来创建全局唯一的对象。工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。原型模式针对创建成本比较大的对象,利用对已有对象进行复制的方式进行创建,以达到节省创建时间的目的。)

首先为什么要使用单例?

单例模式在解决资源竞争问题上,相比其他方法(例如:类级别锁,分布式锁,并发队列 BlockingQueue等),不用创建那么多  对象,一方面节省内存空间,另一方面节省系统文件句柄。

另外,从业务上,有些数据在系统中只应该保存一份,就比较适合设计为单例类。比如,系统的配置信息类。

如何实现一个单例?

1.构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;

2.考虑对象创建时的线程安全问题;

3.考虑是否支持延迟加载;

4.考虑 getInstance() 性能是否高(是否加锁)。


以一个生成随机ID的类为例

1. 饿汉式

public class IdGenerator {

  private AtomicLong id = new AtomicLong(0);

  private static final IdGenerator instance = new IdGenerator();

  private IdGenerator() {}

  public static IdGenerator getInstance() {

    return instance;

  }

  public long getId() {

    return id.incrementAndGet();

  }

}

有人觉得这种实现方式不支持延迟加载,如果实例占用资源多提前初始化实例是一种浪费资源的行为,所以不好。最好的方法应该在用到的时候再去初始化。

也有人觉得如果初始化耗时长,那最好不要等到真正要用它的时候,才去执行这个耗时长的初始化过程,这会影响到系统的性能。一开始就初始化也能避免在程序运行一段时间后,突然因为初始化这个实例占用资源过多,导致系统崩溃,影响系统的可用性。

2. 懒汉式

public class IdGenerator {

  private AtomicLong id = new AtomicLong(0);

  private static IdGenerator instance;

  private IdGenerator() {}

  public static synchronized IdGenerator getInstance() {

    if (instance == null) {

      instance = new IdGenerator();

    }

    return instance;

  }

  public long getId() {

    return id.incrementAndGet();

  }

}

懒汉式的缺点是给 getInstance() 方法加了锁(synchronzed),导致这个函数的并发度很低,相当于串行操作。而这个函数是在单例使用期间,一直会被调用。如果这个单例频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了。

3. 双重检测

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。

public class IdGenerator {

  private AtomicLong id = new AtomicLong(0);

  private static IdGenerator instance;

  private IdGenerator() {}

  public static IdGenerator getInstance() {

    if (instance == null) {

      synchronized(IdGenerator.class) { // 此处为类级别的锁

        if (instance == null) {

          instance = new IdGenerator();

        }

      }

    }

    return instance;

  }

  public long getId() {

    return id.incrementAndGet();

  }

}

有人说,这种实现方式有问题,可能因为指令重排序导致 IdGenerator 对象被 new 出来,并且赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了。

要解决这个问题,需要给 instance 成员变量加上 volatile 关键字,禁止指令重排序才行。

实际上高版本的 Java 已经解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序)。

4. 静态内部类

一种比双重检测更加简单的实现方法,就是利用 Java 的静态内部类。有点类似饿汉式,但又能做到了延迟加载。

public class IdGenerator {

  private AtomicLong id = new AtomicLong(0);

  private IdGenerator() {}

  private static class SingletonHolder{

    private static final IdGenerator instance = new IdGenerator();

  }

  public static IdGenerator getInstance() {

    return SingletonHolder.instance;

  }

  public long getId() {

    return id.incrementAndGet();

  }

}

SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。insance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

5. 枚举

一种最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum IdGenerator {

  INSTANCE;

  private AtomicLong id = new AtomicLong(0);

  public long getId() {

    return id.incrementAndGet();

  }

}

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

推荐阅读更多精彩内容

  • 第一种(懒汉,线程不安全): public class Singleton { private static ...
    Cherry300阅读 145评论 0 2
  • 相信大家都知道设计模式,听的最多的也应该是单例设计模式,这种模式也是在开发中用的最多的设计模式,可能有很多人会写几...
    之后_90阅读 387评论 0 0
  • 定义 一个类只有一个实例,自行实例化并提供给整个系统。 基本思路 将该类构造函数私有化,并通过静态方法获取一个唯一...
    剧透下阅读 232评论 0 0
  • PS:这是一个小白的学习记录之路。大神看见不要笑,狮虎看见不要生气的哈。 题目:单例模式的实现方式 解决思路:狮虎...
    福小满满满阅读 340评论 0 0
  • 简介 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。许多时候整个系统只需要拥有一个的...
    上杉丶零阅读 575评论 0 1