单例模式(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 public class HungryStaticConstants { private HungryStaticConstants () { } private static final HungryStaticConstants instance = new HungryStaticConstants (); 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 public class HungryStaticBlock { private HungryStaticBlock () { } private static final HungryStaticBlock instance; static { instance = new HungryStaticBlock (); } 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 public class LazyThreadUnsafe { private LazyThreadUnsafe () { } private static LazyThreadUnsafe instance; 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 public class LazyThreadSafeSyncMethod { private LazyThreadSafeSyncMethod () { } private static LazyThreadSafeSyncMethod instance; 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 public class LazyThreadSafeSyncBlock { private LazyThreadSafeSyncBlock () { } private static LazyThreadSafeSyncBlock instance; 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 public class DoubleCheckedLock { private DoubleCheckedLock () { } private static volatile DoubleCheckedLock instance; 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 public class StaticInternalClass { private StaticInternalClass () { } private static class SingletonHolder { private static final StaticInternalClass INSTANCE = new StaticInternalClass (); } 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 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 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依赖注入时,使用了双重判断加锁的单例模式。
附:本次演示的项目地址 https://github.com/syshlang/java-design-patterns