1.定义
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。
2.实现方式
2.1 单线程版
public class SingleThreadedSingleton {
// 保存该类的唯一实例
private static SingleThreadedSingleton instance = null;
// 省略实例变量声明
/* * 私有构造器使其他类无法直接通过new创建该类的实例 */
private SingleThreadedSingleton() {
// 什么也不做
}
/** * 创建并返回该类的唯一实例 * 即只有该方法被调用时该类的唯一实例才会被创建 * * @return */
public static SingleThreadedSingleton getInstance() {
if (null == instance) {
instance = new SingleThreadedSingleton();
}
return instance;
}
public void someService() {
// 省略其他代码
}
}
通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,SingleThreadedSingleton的唯一实例只能通过getInstance()方法访问。在多线程环境下,getlnstance()中的 if 语句形成一个 check-then-act 操作,它不是一个原子操作。所以不是线程安全的。
2.2 简单加锁实现
public class SimpleMultithreadedSingleton {
// 保存该类的唯一实例
private static SimpleMultithreadedSingleton instance = null;
/* * 私有构造器使其他类无法直接通过new创建该类的实例 */
private SimpleMultithreadedSingleton() {
// 什么也不做
}
/** * 创建并返回该类的唯一实例 <BR> * 即只有该方法被调用时该类的唯一实例才会被创建 * * @return */
public static SimpleMultithreadedSingleton getInstance() {
synchronized (SimpleMultithreadedSingleton.class) {
if (null == instance) {
instance = new SimpleMultithreadedSingleton();
}
}
return instance;
}
public void someService() {
// 省略其他代码
}
}
在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
而且只有第一次执行此方法时,才真正需要同步。换句话说,一旦创建好对象,就不再需要同步这个方法了,之后每次调用这个方法,同步都是一种累赘。
2.3 双重检查锁定
public class DCLSingleton {
/* * 保存该类的唯一实例,使用volatile关键字修饰instance。 */
private static volatile DCLSingleton instance;
/* * 私有构造器使其他类无法直接通过new创建该类的实例 */
private DCLSingleton() {
// 什么也不做
}
/** * 创建并返回该类的唯一实例 <BR> * 即只有该方法被调用时该类的唯一实例才会被创建 * * @return */
public static DCLSingleton getInstance() {
if (null == instance) {// 操作①:第1次检查
synchronized (DCLSingleton.class) {
if (null == instance) {// 操作②:第2次检查
instance = new DCLSingleton();// 操作③
}
}
}
return instance;
}
public void someService() {
// 省略其他代码
}
}
在执行临界区代码前先检查 instance 是否为 null; 若 instance 不为 null, 则 getlnstance(),直接返回,否则才执行临界区。减少了申请锁的次数。注意一点:instance必须用volatile关键字修饰,否则,一个线程在执行操作①的时候,发现 instance 不为 null, 于是该线程就直接返回这个 instance 变量所引用的实例,而由于JIT编译器的重排序,这个实例可能是另一个线程创建但未初始化完毕的。
2.4 立即创建实例方式
public class Singleton {
private static Singleton uniqueInstance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return uniqueInstance;
}
// other useful methods here
public String getDescription() {
return "I'm a statically initialized Singleton!";
}
}
在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。这种方式不能实现“延迟创建”,如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,可以使用这种方式。
2.5 静态内部类实现
public class StaticHolderSingleton {
// 私有构造器
private StaticHolderSingleton() {
Debug.info("StaticHolderSingleton inited.");
}
static class InstanceHolder {
// 保存外部类的唯一实例
static {
Debug.info("InstanceHolder inited.");
}
final static StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
}
public static StaticHolderSingleton getInstance() {
Debug.info("getInstance invoked.");
return InstanceHolder.INSTANCE;
}
public void someService() {
Debug.info("someService invoked.");
// 省略其他代码
}
public static void main(String[] args) {
StaticHolderSingleton.getInstance().someService();
}
}
类的静态变量被初次访问会触发 Java 虚拟机对该类进行初始化,即该类的静态变量的值会变为其初始值而不是默认值 。 因此,静态方法 getlnstance()被调用的时候 Java 虚拟机会初始化这个方法所访问的内部静态类 InstanceHolder 。 这使得 InstanceHolder 的静态变量INSTANCE 被初始化,从而使 StaticHolderSingleton 类的唯一实例得以创建。
由于类的静态变蜇 只会创建一次,因此 StaticHolderSingleton (单例类)只会被创建一次。
2.6 利用枚举类实现
public class EnumBasedSingletonExample {
public static void main(String[] args) {
Thread t = new Thread() {
@Override
public void run() {
Debug.info(Singleton.class.getName());
Singleton.INSTANCE.someService();
};
};
t.start();
}
public static enum Singleton {
INSTANCE;
// 私有构造器
Singleton() {
Debug.info("Singleton inited.");
}
public void someService() {
Debug.info("someService invoked.");
// 省略其他代码
}
}
}
这里,枚举类型 Singleton 相当于一个单例类,其字段 INSTANCE 值相当于该类的唯一实例。这个实例是在 Singleton.INSTANCE 初次被引用的时候才被初始化的。 仅访问 Singleton 本身(比如 Singleton.class.getName()调用)并不会导致 Singleton 的唯一实例被初始化 。