单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
本文将对单例模式进行较详细的介绍,主要包含如下几个部分的内容:
一、单例模式的意图和要点
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模型。
从上述描述中包含了3个要点:
根据单例的创建方式而言,单例的实现可以分为饿汉式和懒汉式两种。其中:
饿汉式是单例类被加载的时候就去创建一个单例实例,而懒汉式是在真正需要的时候才去创建单例实例。
接下来,我们一起来看一下饿汉式和懒汉式的单例模式,并给出一些JDK源码中使用单例模式的代码示例。
饿汉式大致的图如下:
根据上述类图结构,可以编写类似如下的代码:
public class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {
}
public static EagerSingleton getInstance() {
return INSTANCE;
}
}
代码特点:
饿汉式在JDK中,比较典型的就是Runtime类:
懒汉式单例和饿汉式单例的区别在于,懒汉式不是马上创建一个实例,而是在第一次被引用时才去创建单例实例。
懒汉式的类图结构如下:
根据上述类图结构,可以编写类似如下的代码:
package com.wangmengjun.tutorial.designpattern;
public class LazySingleton {
private static LazySingleton INSTANCE = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
代码特点:
当Singleton类被加载的时候,instance并不实例化,当静态方法getInstance()调用的时候才去判断要不要实例化,这个过程是延迟的,直到使用时才实例化,所以叫懒汉式。
懒汉式在JDK中,比较典型的就是Desktop类:
接下来,我们看下如下懒汉式代码是否能保证创建单例实例的安全性,也即是否只会创建只有一个单例?
上述代码存在一个问题:
如果多个线程同时调用getInstance()方法,线程A调用的时候instance为null,而线程B调用方的时候instance也是null。这种就可能导致线程A和线程B各自创建了一个Singleton实例,会导致单例失败,创建多余一个实例。
可以简单用如下多线程下调用实例来验证一下:
package com.wangmengjun.tutorial.designpattern;
public class SingletonPatternMain {
public static void main(String[] args) {
for(int i=0; i<15;i++) {
Thread t = new Thread() {
@Override
public void run() {
System.out.println(LazySingleton.getInstance().toString());
}
};
t.start();
}
}
}
某次执行结果,发现产生不止一个实例:
那么,如何保证单例在多线程环境下也是安全的呢?接下来,我们就来看看保证单例安全性的多种实现。
这种方式和上述提供的JDK中的Desktop类实现一致。
4.2 double check机制
4.3 采用内部类Holder Lazy class
4.4 采用枚举来实现单例
package com.wangmengjun.tutorial.designpattern;
public enum EnumSingleton {
INSTANCE;
private EnumSingleton() {
}
public void saySomthing(String name) {
System.out.println("Hello, " + name);
}
}
直接使用即可。
相信通过上述几个方面的讲解,读者应该对单例模式的实现、懒汉式在多线程下可能出现的问题、以及单例安全性保证的多种方法都有所了解了。
有兴趣的读者可以动手试试,相信你会获取的更多。