设计模式2——单例模式(singleton),五种构建方法(最喜欢枚举和内部类方式)

单例模式,在配置文件的时候用的比较多,各处的配置都保持统一性。

一、什么是单例模式?

layout title folder permalink categories tags

pattern

Singleton

singleton

/patterns/singleton/

Creational

Java

Gang Of Four

Difficulty-Beginner

布局 标题 文件夹 永久链接 分类 tags

模式

单例

singleton

/patterns/singleton/

构建累

Java

四人帮

入门级难度

在设计模式中,《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书)由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)"。GoF就是Gang of Four

Intent

含义

Ensure a class only has one instance, and provide a global point of access to it.

确保一个类只有一个实例,并且提供一个方法来获取它。

Explanation

解释

Real world example

真实案例

There can only be one ivory tower where the wizards study their magic. The same enchanted ivory tower is always used by the wizards. Ivory tower here is singleton.

这里只能有一个巫师学习魔法的象牙塔。同样加强的象牙塔,被巫师经常使用。象牙塔在这就是单例。

In plain words

简单地说:

Ensures that only one object of a particular class is ever created.

确保特定类只有一个对象被创建出来。

Wikipedia says

维基百科(Wikipedia)说:

In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.

在软件工程,单例模式是一种限制类仅实例化一个对象的软件设计模式。在仅需一个对象来调整系统反应时,这个模式是很有用的。

Programmatic Example

程序案例

Joshua Bloch, Effective Java 2nd Edition p.18

Effective Java 2nd (嘿嘿,这本书我也有)

A single-element enum type is the best way to implement a singleton

一个但元素枚举类型,是单例模式的最佳实现

public enum EnumIvoryTower {
  INSTANCE;
}

Then in order to use

然后使用单例(可以啊,以前我还没试过这种方式,学到了!):

EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE;
assertEquals(enumIvoryTower1, enumIvoryTower2); // true

Applicability

应用

Use the Singleton pattern when

当如下情况时,用到单例模式:

  • there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point
  • when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code
  • 一个类必须恰好有一个实例,并且它必须提供给用户一个熟知的调用入口‘
  • ’当这个唯一的对象需要被子类继承时,而且用户需要能够使用一个被继承的实例而不用修改他们的代码。

Typical Use Case

典型使用案例

  • the logging class
  • managing a connection to a database
  • file manager
  • 日志类
  • 在数据库中,管理连接
  • 文件管理

Real world examples

真实案例

Consequences

经验结论

  • Violates Single Responsibility Principle (SRP) by controlling their own creation and lifecycle.
  • Encourages using a global shared instance which prevents an object and resources used by this object from being deallocated.
  • Creates tightly coupled code. The clients of the Singleton become difficult to test.
  • Makes it almost impossible to subclass a Singleton.
  • 控制他们的构建和生命周期,违反单一功能原则(SRP)
  • 鼓励使用一个全局实例,它能防止一个对象和资源被这个对象使用而解除分配
  • 写紧耦合代码。使用单例的用户会难以测试。
  • 让继承一个单例变得几乎不可能

Credits

二、代码分析与讲解

编程最好玩的地方就在于可以立马看到结果,还能自己控制,有结果的工作是很容易让人开心的!

让我们让看一看这一段代码:

1,单例模式说明

/**
 * Singleton pattern ensures that the class can have only one existing instance per Java classloader
 * instance and provides global access to it.

单例模式保证了一个类在每个java类加载实例汇中只能有一个存在的实例,而且提供全局的调用方法。


 * <p>
 * One of the risks of this pattern is that bugs resulting from setting a singleton up in a
 * distributed environment can be tricky to debug, since it will work fine if you debug with a
 * single classloader. Additionally, these problems can crop up a while after the implementation of
 * a singleton, since they may start out synchronous and only become async with time, so you it may
 * not be clear why you are seeing certain changes in behaviour.

这个模式的一个风险是在分布式环境下设立单例模式时,debug非常棘手,因为在只有一个类加载器时它是正常的!(记得这一点,说不定就遇上了)。而且,这些问题在继承了一个单例累后会突然出现,因为他们可能同步出发,然后只是偶尔异步。所以你也许对于这些变化不是非常明了。


 * <p>
 * There are many ways to implement the Singleton. The first one is the eagerly initialized instance
 * in {@link IvoryTower}. Eager initialization implies that the implementation is thread safe. If
 * you can afford giving up control of the instantiation moment, then this implementation will suit
 * you fine.

这里有很多办法来实现单例。第一种办法是急切的加载实例(感觉是饥汉式)。急切的初始化暗示了这种实现方式是线程安全的。如果你能够承受得了实例化时间的耗费,这种实现方式会适合你。


 * <p>
 * The other option to implement eagerly initialized Singleton is enum based Singleton. The example
 * is found in {@link EnumIvoryTower}. At first glance the code looks short and simple. However, you
 * should be aware of the downsides including committing to implementation strategy, extending the
 * enum class, serializability and restrictions to coding. These are extensively discussed in Stack
 * Overflow:
 * http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing
 * -a-singleton-with-javas-enum

另一种实现先初始化单例的方式是枚举类式的单例。在EnumIvoryTower可以看到。第一眼看过去,代码又短又简单。然而,你应该要清楚,缺点包括提交的实现策略,继承枚举类,序列化和编码限制。这些扩展行阅读可以在Stack Overflow看到:

http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing-a-singleton-with-javas-enum


 * <p>
 * {@link ThreadSafeLazyLoadedIvoryTower} is a Singleton implementation that is initialized on
 * demand. The downside is that it is very slow to access since the whole access method is
 * synchronized.

ThreadSafeLazyLoadedIvoryTower 是一种在需要时才初始化的一种单例实现方式(饱汗式)。缺点在于它的调用是非常慢的,因为所有的获取方法是同步的。


 * <p>
 * Another Singleton implementation that is initialized on demand is found in
 * {@link ThreadSafeDoubleCheckLocking}. It is somewhat faster than
 * {@link ThreadSafeLazyLoadedIvoryTower} since it doesn't synchronize the whole access method but
 * only the method internals on specific conditions.

另一种单例的实现方式,是按需初始化,在ThreadSafeDoubleCheckLocking可以看到。它是一种比ThreadSafeLazyLoadedIvoryTower更快的实现方式,因为在获取对象方法时它不是同步的,只有方法里的特定条件下才是同步的。


 * <p>
 * Yet another way to implement thread safe lazily initialized Singleton can be found in
 * {@link InitializingOnDemandHolderIdiom}. However, this implementation requires at least Java 8
 * API level to work.
 */

还有另一种实现线程安全的懒加载单例的方式能在InitializingOnDemandHolderIdiom看到。然而,这种实现方式要求至少是Java 8 API才能实现。

2,一行一行读代码

单例模式的核心在于创建对象的时候做出种种防御措施,确保一个类只有一个对象。原来一直记得饥汉式和饱汉的实现方式,但这里使用了五种实现方式,让我们来逐一看看。代码在singleton

(1)eagerly initialized singleton

// eagerly initialized singleton

这个大概就是饥汉式咯,一开始不管三七二十一,先把对象new出来。

仔细看:

IvoryTower ivoryTower1 = IvoryTower.getInstance();

也就是说获取对象不是new而是用getInstance,这个方式在工厂中也有用到

  public static IvoryTower getInstance() {
    return INSTANCE;
  }

那么INSTANCE从哪里来?

private static final IvoryTower INSTANCE = new IvoryTower();

也就是说在加载类信息时就会初始化这个对象,后续都不会再初始化了。

所以当打印出这两个对象时,对象地址是一样的

11:43:43.150 [main] INFO com.iluwatar.singleton.App - ivoryTower1=com.iluwatar.singleton.IvoryTower@1936f0f5
11:43:52.538 [main] INFO com.iluwatar.singleton.App - ivoryTower2=com.iluwatar.singleton.IvoryTower@1936f0f5

这种方式是最简单的,也很好理解,但是在多线程、分布式时操作起来会有问题,比如有两个类加载器,那是不是就有两个单例了?

(2)lazily initialized singleton

lazily initialized singleton 也可以叫做饱汉式单例吧。老是说,什么鬼饥汉、饱汉,完全就是图个形象,单对于理解没什么用。lazy明显好理解好吗,lazy一般就可以理解成延迟加载了。

ThreadSafeLazyLoadedIvoryTower threadSafeIvoryTower1 =
        ThreadSafeLazyLoadedIvoryTower.getInstance();

它获取单例的函数:

  public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {
    if (instance == null) {
      instance = new ThreadSafeLazyLoadedIvoryTower();
    }

    return instance;
  }

再来看下它的构造函数:

  private ThreadSafeLazyLoadedIvoryTower() {
    // protect against instantiation via reflection
    if (instance == null) {
      instance = this;
    } else {
      throw new IllegalStateException("Already initialized.");
    }
  }

第一单例的构造函数,都是private,不允许直接调用,避免了new对象带来的多例;第二,同步机制避免了同时创建对象造成不一致问题,但是方法同步限制了效率。比较巧妙的是在构造方法中,还加了一步对instance的限制,这里的注释说的是避免反射造成的影响。第四,方法和变量都是static,因为它的调用是通过类而不是对象。

(3)enum singleton

枚举型的单例,以前我是没见过,也算涨见识了。先来看看调用:

EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;

这种方式是在effect  java中的一种实现方式(顿时想把这本书翻一遍,很灵性有木有),它的实现方式很简单,只用在enum中定义:

INSTANCE;

这样就可以了,而且它是线程安全的!为啥是线程安全?感觉得要看看enum,也看过一些代码里还喜欢用enum。

(4)double checked locking

这也是effective java的一个案例,这种方式能提升本地变量25%的效率,核心代码是这一段:

  public static ThreadSafeDoubleCheckLocking getInstance() {   
    ThreadSafeDoubleCheckLocking result = instance;
    if (result == null) {
      synchronized (ThreadSafeDoubleCheckLocking.class) {
        result = instance;
        if (result == null) {
          instance = result = new ThreadSafeDoubleCheckLocking();
        }
      }
    }
    return result;
  }

它只在空的时候才进行线程同步,但是这里很有意思,判断了两次result == null,

第一次判断 result 是否为null,是判断单例是否被初始化,它如果没有被初始化,我们也不能简单地进行初始化,因为可能有其他线程进来了,所以就要加同步。synchronized (ThreadSafeDoubleCheckLocking.class)之后,再用本地的instance变量检查单例是否被初始化了。巧妙地在于第二次检查,已经通过了第一次的result == null 之后,可能卡在synchronized,所以需要进到同步代码后再检查一遍。

double  check的关键在于第二次检查,防止被阻塞的代码重复初始化。这段代码对于非空情况,只用检查一次,效率可以大大提升。

(5)initialize on demand holder idiom

这种方式,按需初始化,是一种安全的懒加载模式。这种技术,尽可能得“懒”,并可以在所有的java版本中应用,它利用了类初始化的语法检查,因此在各种java编译器和虚拟机上都可以运行。

这么神奇?!因为它用了内部类!仔细看:

  public static InitializingOnDemandHolderIdiom getInstance() {
    return HelperHolder.INSTANCE;
  }

获取实例的方式没什么两样,这个HelperHolder是什么东东?它是 InitializingOnDemandHolderIdiom的一个内部类

  private static class HelperHolder {
    private static final InitializingOnDemandHolderIdiom INSTANCE =
        new InitializingOnDemandHolderIdiom();
  }

擦,这是我见过用内部类写的第一段神奇的代码,还可以这么用!为什么可以呢?因为内部类被引用不会早于getInstance方法被调用(因此,不会被类装载器更早加载)。因此这种解决方案是线程安全的,却不用特别的语法构造(像volatile和synchronized)。

为什么这样实现就是单例的?

                 因为这个类的实例化是靠静态内部类的静态常量实例化的。

                 INSTANCE 是常量,因此只能赋值一次;它还是静态的,因此随着内部类一起加载。

贼全五种单例构造方法,顿时感觉到学习语言的魅力,一个简单的设计模式有如此多的实现方式,佩服。我个人比较喜欢枚举和initialize on demand holder idiom 实现方式。

猜你喜欢

转载自blog.csdn.net/qq_22059611/article/details/85236553