为了知道大牛是如何应用设计模式的,我喜欢扒一扒知名项目中的源码。
单例模式的使用场景其实还挺简单,就是一个类只允许创建一个对象,全局共享使用这个对象。
在 Java 中实现单例,需要考虑是否懒加载、是否线程安全的问题,实现方式:饿汉式、懒汉式、双重检查、静态内部类、枚举。具体实现代码
开源使用实例一
JDK 中 java.lang.Runtime 类,每个运行中的 Java 应用的环境信息,单例。
看下它的注释:
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
看下它的部分源码:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
.
.
.
}
这是单例模式经典的实现方式之一:饿汉式
开源使用实例二
Spring 的单例 bean 的实现。Spring 的单例是 IoC 容器级别的,这句话啥意思呢?
ApplicationContext context = new ClassPathXmlApplicationContext("beanFactoryTest.xml");
MyTestBean bean1 = (MyTestBean)context.getBean("myTestBean");
MyTestBean bean2 = (MyTestBean)context.getBean("myTestBean");
System.out.println(bean1 == bean2); // 打印true
ApplicationContext context2 = new ClassPathXmlApplicationContext("beanFactoryTest.xml");
MyTestBean bean3 = (MyTestBean)context2.getBean("myTestBean");
System.out.println(bean1 == bean3); // 打印false
代码中 bean1 = bean2,因为 Spring 默认是单例,且 bean1 和 bean2 都取自 context 实例中容器;
bean1 != bean3,因为 bean1 和 bean3 分别取自不同的容器。
那 Spring 是如何实现单例的呢?
看下 org.springframework.beans.factory.support.AbstractBeanFactory 获取 bean 的代码
protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException {
.
.
.
//获取缓存的 bean 实例
Object sharedInstance = getSingleton(beanName);
.
.
.
//创建 bean 实例
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
.
.
.
}
1、Srping IoC 容器在初始化的时候,若未配置懒加载,单例 bean 实例会在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 创建,
createBean -> doCreateBean -> createBeanInstance
2、单例 bean 实例获取的逻辑在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry 的 getSingleton 方法
public class DefaultSingletonBeanRegistry {
//缓存单例 bean 实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从 ConcurrentHashMap 中获取 bean 实例
Object singletonObject = this.singletonObjects.get(beanName);
.
.
.
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
可见 Spring 的单例 bean 实例的创建,是通过代码自己控制的,单例 bean 实例保存在一个 ConcurrentHashMap 中,而不是上述单例模式的典型实现方式。
这里说的 Spring 的单例是在 IoC 容器中,实际开发中有时候还需要考虑线程间的单例和分布式中多进程间的单例。线程间的单例可以使用 ThreadLocal 实现,进程间的单例要借助分布式锁以及对单例实例进行序列化存储与反序列化。