美文网首页
单例模式有哪些实现方法

单例模式有哪些实现方法

作者: 兮兮码字的地方 | 来源:发表于2020-02-28 21:44 被阅读0次

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

首先为什么要使用单例?

单例模式在解决资源竞争问题上,相比其他方法(例如:类级别锁,分布式锁,并发队列 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();

  }

}

相关文章

  • python面试题-2018.1.30

    问题:如何实现单例模式? 通过new方法来实现单例模式。 变体: 通过装饰器来实现单例模式 通过元类来创建单例模式...

  • 单例模式有哪些实现方法

    (单例是创建型模式的一种。创建型模式主要解决对象的创建问题,封装复杂的创建过程,解耦对象的创建代码和使用代码。其中...

  • 单例模式

    1.利用装饰器实现单例模式 2.修改new方法实现单例模式 3.利用元类实现单例模式 总结: 用装饰器和元类实现的...

  • Python之单例模式总结

    一、单例模式 a、单例模式分为四种:文件,类,基于__new__方法实现单例模式,基于metaclass方式实...

  • 单例模式(单例宏)

    单例模式 单例模式(arc) 类的实现 调用单例 单例模式(mrc) 除了上边的方法我们在mrc的时候还需要增加一...

  • 单例模式和装饰器

    new方法实现单例模式 装饰器

  • 单例模式(使用同步方法)

    主方法 单例模式实现类 线程

  • C++中线程安全的单例模式(2)

    简介 本文介绍单例模式实现的另外两种方法: 通过double check(借助std::atomic)实现单例模式...

  • 单例设计模式

    单例设计模式 单例设计模式介绍 单例设计模式的八种方法2.1 饿汉式(静态常量)2.1.1 实现步骤2.1.2 代...

  • java设计模式-单例模式(singleton)

    单例模式(singleton),属于创建型模式 单例模式实现方法有很多种,今天要讲的是其中两种,也是最简单,最常见...

网友评论

      本文标题:单例模式有哪些实现方法

      本文链接:https://www.haomeiwen.com/subject/tcbrhhtx.html