syshlang设计模式之创建型模式中的单例模式(Singleton Pattern) | 浪迹一生

所谓拥有,皆非束缚;所有过往,皆为序章。

0%

设计模式之创建型模式中的单例模式(Singleton Pattern)

    单例模式(Singleton Pattern)是我们平时开发中用的比较多的一种设计模式,如上图所示,所谓类的单例设计模式,就是确保在整个的软件系统中一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,该类只提供一个取得其对象实例的静态方法。

单例设计模式八种方式

饿汉式

采用静态常量实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author sunys
*/
public class HungryStaticConstants {
/**
* 构造器私有化,防止new
*/
private HungryStaticConstants() {
}

/**
* 类内部创建静态对象实例
*/
private static final HungryStaticConstants instance = new HungryStaticConstants();

/**
* 提供一个公有的取得其对象实例的静态方法
* @return
*/
public static HungryStaticConstants getInstance(){
return instance;
}
}

    这种方式,在类装载时instance就被实例化,由于类装载原因的不确定性,也就像这种方式的名字所表达的意思一样,饿汉式,没有达到懒加载(lazy loading)的效果,并且还可能造成内存浪费。由于是基于classloder机制的,避免了多线程的同步问题,是多线程安全的,并且没有锁机制,因此执行效率会提高。

采用静态代码块实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @author sunys
*/
public class HungryStaticBlock {
/**
* 构造器私有化,防止new
*/
private HungryStaticBlock() {
}
private static final HungryStaticBlock instance;
/**
* 在静态代码块中,创建单例对象
*/
static {
instance = new HungryStaticBlock();
}

/**
* 提供一个公有的取得其对象实例的静态方法
* @return
*/
public static HungryStaticBlock getInstance() {
return instance;
}
}

    这种方式和上面的方式差不多,只不过是将类的实例化放在了静态代码块中进行,static块的执行发生在初始化的阶段。初始化阶段,jvm主要完成对静态变量的初始化,静态块执行等工作,也就是说instance在这个时候被实例化,其优缺点和上面的方式是一样。

懒汉式

线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author sunys
*/
public class LazyThreadUnsafe {
/**
* 构造器私有化,防止new
*/
private LazyThreadUnsafe() {
}
private static LazyThreadUnsafe instance;

/**
* 提供一个静态的公有方法,当使用到该方法时,才去实例化instance
* @return
*/
public static LazyThreadUnsafe getInstance() {
if (instance == null){
instance = new LazyThreadUnsafe();
}
return instance;
}
}

    与饿汉式不一样的地方,显然这种方式达到了懒加载(lazy loading)的效果。但是由于这种方式没有加锁 synchronized,它不是多线程安全的,在多线程下会有产生多个实例的可能。

线程安全

采用同步方法的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @author sunys
*/
public class LazyThreadSafeSyncMethod {
/**
* 构造器私有化,防止new
*/
private LazyThreadSafeSyncMethod() {
}
private static LazyThreadSafeSyncMethod instance;

/**
* /提供一个静态的公有方法,加入同步锁,解决线程安全问题
* @return
*/
public static synchronized LazyThreadSafeSyncMethod getInstance() {
if (instance == null){
instance = new LazyThreadSafeSyncMethod();
}
return instance;
}
}

    很显然,这种方式达到了懒加载(lazy loading)的效果,并且对方法加了同步锁,解决了线程安全问题,但是每个线程在获得实例执行getInstance()方法时都要进行同步,效率很低。

采用同步代码块实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author sunys
*/
public class LazyThreadSafeSyncBlock {
/**
* 构造器私有化,防止new
*/
private LazyThreadSafeSyncBlock() {
}
private static LazyThreadSafeSyncBlock instance;

/**
* 提供一个静态的公有方法,对代码块加入同步锁,解决线程安全问题
* @return
*/
public static LazyThreadSafeSyncBlock getInstance() {
if (instance == null){
synchronized (LazyThreadSafeSyncBlock.class){
instance = new LazyThreadSafeSyncBlock();
}
}
return instance;
}
}

    这种方式和上面的方式差不多,只不过是将同步锁从方法上移到了代码块上,实际效果也不理想。

双检锁/双重校验锁(DCL:double-checked locking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @author sunys
*/
public class DoubleCheckedLock {
/**
* 构造器私有化,防止new
*/
private DoubleCheckedLock() {
}
private static volatile DoubleCheckedLock instance;

/**
* 提供一个静态的公有方法,加入双重检查代码和同步锁,解决线程安全问题,同时解决懒加载问题
* @return
*/
public static DoubleCheckedLock getInstance() {
if (instance == null){
synchronized (DoubleCheckedLock.class){
if (instance == null){
instance = new DoubleCheckedLock();
}
}
}
return instance;
}
}

    这种方式可以看作是上面两种方式的结合体或者升级方式,这种方式采用了双重校验加锁的机制,既达到了懒加载(lazy loading)的效果,又保证了线程安全,而且效率较高。

    volatile是一个类型修饰符(type specifier).volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile关键字的两层语义:

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的;
  • 禁止进行指令重排序。

登记式/静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @author sunys
*/
public class StaticInternalClass {
/**
* 构造器私有化,防止new
*/
private StaticInternalClass() {
}

/**
* 提供一个静态内部类,
* 该类中有一个静态属性StaticInternalClass
*/
private static class SingletonHolder {
private static final StaticInternalClass INSTANCE = new StaticInternalClass();
}

/**
* 提供一个静态的公有方法,直接返回内部类的静态属性
* @return
*/
public static StaticInternalClass getInstance() {
return SingletonHolder.INSTANCE;
}
}

    首先,这种方式采用了类装载的机制来保证初始化实例时只有一个线程(在类进行初始化时,别的线程是无法进入的),巧妙的解决了线程安全问腿;其次,静态内部类的方式在SingletonHolder类被装载时并不会立即实例化,而是在需要实例化(调用getInstance方法)时,才会装载SingletonHolder类,从而完成StaticInternalClass的实例化,也很巧妙的达到了懒加载(lazy loading)的效果。当然,也不会存在效率低的问题。

枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author sunys
*/
public class SingletonEnum {
public static void main(String[] args) {
Instance instance = Singleton.INSTANCE.instance;
}
private static class Instance{}
enum Singleton{
INSTANCE;
private Instance instance;
Singleton() {
instance = new Instance();
}
}

}

    首先,枚举类的构造方法限制为私有的,我们访问枚举类的实例时会执行它的构造方法,并且每个枚举类的实例都是static final类型的,也就是只能被实例化一次,实际也就是通过类加载机制保证了线程安全。

单例模式在Spring框架中的应用

    先上源码,org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

DefaultSingletonBeanRegistry.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
   /**
* Return the (raw) singleton object registered under the given name.
* <p>Checks already instantiated singletons and also allows for an early
* reference to a currently created singleton (resolving a circular reference).
* @param beanName the name of the bean to look for
* @param allowEarlyReference whether early references should be created or not
* @return the registered singleton object, or {@code null} if none found
*/
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

    如上,Spring依赖注入Bean实例默认是单例的,Spring的依赖注入主要是在org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String)方法中,getBean中调用的 doGetBean 方法调用了getSingleton 进行bean的创建,从上面代码可以看到,spring依赖注入时,使用了双重判断加锁的单例模式。

------------- The End -------------
  • 本文作者: 浪迹一生
  • 本文链接: https://www.syshlang.com/fc936307/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!