设计模式1——单例模式(singleton)及在框架中的应用

目录

一、什么是单例模式?

Intent

Explanation

Applicability

Typical Use Case

Real world examples

Consequences

Credits

二、代码分析与讲解

1,单例模式说明

2,一行一行读代码

(1)eagerly initialized singleton

(2)lazily initialized singleton

(3)enum singleton

(4)double checked locking

(5)initialize on demand holder idiom

三、单例模式的实现方式

1,懒汉模式

2,饿汉模式(静态常量模式,也有用静态代码块实现)

3,双重锁模式

4,静态内部类单例模式

5,枚举单例模式

四、典型应用

1,java.lang.RuntimeRuntime类时典型的饿汉式单例模式。public class Runtime {    private static Runtime currentRuntime = new Runtime();

2,spring依赖注入生成单例

五、使用场景


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

一、什么是单例模式?

layouttitlefolderpermalinkcategoriestags

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 实现方式。

三、单例模式的实现方式

1,懒汉模式

线程不安全,延迟初始化,严格意义上不是不是单例模式,if (instance == null)这一句在多线程下不能保证单例。
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

还有一种将getInstance改为,同步方式,但效率低,但它是能实现单例的:
    public static synchronized Singleton getInstance()

懒汉式还有一种尝试,把锁加在方法里面:
    public static Singleton getInstance() {  
    if (instance == null) {  
synchronized(Singleton .class) {
            instance = new Singleton();  
}
    }  
    return instance;  
    }  
这种方法实际上不能实现单例,比如有多个线程走到了走到了synchronized这一行,他们会依次初始化。乃至这种方式下采用双重锁方式,照样不能实现单例,因为初始化有3个步骤:分配内存空间、引用指向空间、初始化对象,可能存在没初始化对象的情况下另一个线程进来(加了volatile 是可以的,就是后面讲的双重检查是ok的):
public static Singleton getInstance() {  
    if (instance == null) {  
      synchronized(Singleton .class) {
              if (instance == null) {  
                   instance = new Singleton();  
              }
          }
      }  
    return instance;  
}  

详解:
sInstance =new Singleton();这句话创建了一个对象,他可以分解成为如下3行代码:

 

1

2

3

memory = allocate();  // 1.分配对象的内存空间

ctorInstance(memory);  // 2.初始化对象

sInstance = memory;  // 3.设置sInstance指向刚分配的内存地址

  上述伪代码中的2和3之间可能会发生重排序,重排序后的执行顺序如下

1

2

3

memory = allocate();  // 1.分配对象的内存空间

sInstance = memory;  // 2.设置sInstance指向刚分配的内存地址,此时对象还没有被初始化

ctorInstance(memory);  // 3.初始化对象

  因为这种重排序并不影响Java规范中的规范:intra-thread sematics允许那些在单线程内不会改变单线程程序执行结果的重排序。

  但是多线程并发时可能会出现以下情况

 

线程B访问到的是一个还未初始化的对象。

2,饿汉模式(静态常量模式,也有用静态代码块实现)

线程安全,比较常用,但容易产生垃圾,因为一开始就初始化。

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

 

3,双重锁模式

 

线程安全,延迟初始化。这种方式采用双锁机制,安全且在多线程情况下能保持高性能。将对象声明为volatitle后,重排序在多线程环境中将会被禁止

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

双重检查模式,进行了两次的判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton=new Singleton()对象的创建在JVM中可能会进行重排序,在多线程访问下存在风险,使用volatile修饰signleton实例变量有效,解决该问题。

4,静态内部类单例模式

 

public class Singleton { 
    private Singleton(){
    }
      public static Singleton getInstance(){  
        return Inner.instance;  
    }  
    private static class Inner {  
        private static final Singleton instance = new Singleton();  
    }  

只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。

 

5,枚举单例模式

 

public enum Singleton {
    INSTANCE;
}

默认枚举实例的创建是线程安全的,并且在任何情况下都是单例。实际上

枚举类隐藏了私有的构造器。
枚举类的域 是相应类型的一个实例对象
那么枚举类型日常用例是这样子的:

public enum Singleton  {
    INSTANCE 
 
    //doSomething 该实例支持的行为
      
    //可以省略此方法,通过Singleton.INSTANCE进行操作
    public static Singleton get Instance() {
        return Singleton.INSTANCE;
    }
}

枚举单例模式在《Effective Java》中推荐的单例模式之一。但枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。

在以上所有的单例模式中,推荐静态内部类单例模式。主要是非常直观,即保证线程安全又保证唯一性。

众所周知,单例模式是创建型模式,都会新建一个实例。那么一个重要的问题就是反序列化。当实例被写入到文件到反序列化成实例时,我们需要重写readResolve方法,以让实例唯一。

private Object readResolve() throws ObjectStreamException{
        return singleton;
}

四、典型应用

1,java.lang.Runtime
Runtime类时典型的饿汉式单例模式。
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {
    }
......
}

 

2,spring依赖注入生成单例

 

 

五、使用场景

需要频繁地进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

 

 

 

 

 

参考:

1,Java并发变成艺术

2,传统单例模式双重检查锁存在的问题

3,github  design-pattern

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值