单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
应用场景
Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~
windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
网站的计数器,一般也是采用单例模式实现,否则难以同步。
应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
代码应用
Jdk 源码中单例模式
public class SingletonTest1{
public static void main(String args[]){
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
System.out.println(singleton1==singleton2);
}
}
class Singleton{
//私有化构造方法;外部不能new
private Singleton(){
}
//类内部创建一个对象实例
private final static Singleton instance =new Singleton();
//定义一个公有的静态方法,返回类实例对象
public static Singleton getInstance(){
return instance;
}
}
(3) 在Runtime类中使用了单例模式:
public class Runtime {
//静态方法的方式创建对象
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
//在方法中调用创建好的静态方法currentRuntime
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
优缺点
优点:写法简单,就是在类装载的时候就完成实例化,避免了线程同步问题。。
缺点:在类装载的时候就完成实例化,没有达到懒加载的效果,若没有在整个过程中没有使用这个实例,造成内存的浪费。
(3)基于classloader机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中多数是调用getInstance方法,但是导致类装载的原因很多,不能确定有其他方式(或者其他的静态方法)导致类加载,这时初始化instance就没有达到lazy loading的效果。
(4)这种单例模式可以使用,但是可能造成内存浪费。
spring 源码中单例模式
在 Spring 依赖注入 Bean 实例默认是单例的,我们由此展开。bean 可以被定义为两种模式:prototype(原型|多例)和 singleton(单例)。
singleton(单例):只有一个共享的实例存在,所有对这个 bean 的请求都会返回唯一的实例。
prototype(多例):对这个 bean 的每次请求都会创建一个新的 bean 实例,类似于 new。
Spring 中加载单例的过程都是在 BeanFactory 的 getBean() 方法中被定义的,其默认的功能在 AbstractBeanFactory 类中实现,主要包含两个功能。
从缓存中获取单例 Bean。
Bean 的实例中获取对象。
getBean() 方法最终会调用 AbstractBeanFactory 的 doGetBean() 方法,源码如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
//对传入的beanName稍作修改,防止有一些非法字段,然后提取Bean的Name
final String beanName = transformedBeanName(name);
Object bean;
//直接从缓存中获取单例工厂中的objectFactory单例
Object sharedInstance = getsingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
if (isSingletonCurrentlyInCreation(beanName)) {
logger.debug("Returning eagerly cached instance of singleton bean '" +
beanName + "' that is not fully initialized yet - a consequence of a circular reference");
} else {
}
}
//返回对应的实例,从 Bean实例中获取对象
bean = getObjectForBeanInstance(sharedInstance,name,beanName, null);
} else {
...
}
...
}
getBean() 方法不仅处理单例对象的逻辑,还处理原型对象的逻辑。继续看 getSingleton() 方法的代码实现。getSingleton() 的工作流程:singletonObjects-->earlySingletonObjects-->singletonFactories-->创建单例实例
/**
* 单例对象的缓存
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//首先通过名字查找这个Bean是否存在
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//查看缓存中是否存在这个Bean
singletonObject = this.earlySingletonObjects.get(beanName);
//如果这个时候的Bean实例还为空并且允许懒加载
if (singletonObjects == 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;
}
上面代码片段中,synchronized(this.singletonObjects) 是关键,但是前提条件 isSingletonCurrentlyInCreation 的返回值也是 true,也就是这个 Bean 正在被创建。因此,第一次调用 doGetBean() 的时候,getSingleton() 基本上都是返回 null,所以会继续执行 doGetBean() 方法中后面的逻辑。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 获取beanDefinition
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// Create bean instance.
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
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);
}
}
...
}
}
可以看到,在 BeanFactory 中,从 XML 中解析出来的相关配置信息被放在 BeanDefinitionMap 中,通过这个 Map 获取 RootBeanDefinition,然后执行判断语句 if(mbd.isSingleton())。如果是单例的,则接着调用 getSingleton() 的重载方法,传入 mbd 参数。当从缓存中加载单例对象时,会把当前的单例对象在 singletonObjects 中存放一份,这样可以保证在调用 getBean() 方法的时候,singletonObjects 中永远只有一个实例,在获取对象时才会给它分配内存,既保证了内存高效利用,又是线程安全的。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// 直接从缓存中获取单例Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName,
"Singleton bean creation not allowed while singletons of this factory are in destruction " +
"(Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// Has the singleton object implicitly appeared in the meantime ->
// if yes, proceed with it since the exception indicates that state.
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
throw ex;
}
}
catch (BeanCreationException ex) {
if (recordSuppressedExceptions) {
for (Exception suppressedException : this.suppressedExceptions) {
ex.addRelatedCause(suppressedException);
}
}
throw ex;
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 在singletonObject中添加要加载的单例
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
如此一来,当下次需要这个单例 Bean 时,可以直接从缓存中获取。在 Spring 中创建单例的过程虽然有点绕,但是逻辑非常清楚,就是将需要的对象放在 Map 中,下次需要的时候直接从 Map 中获取即可。
mybaties获取单例对象
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
private ErrorContext() {
}
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
总结以上,不难看出:
单例模式应用的场景一般发现在以下条件下:
(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。