Java高并发 - 14.单例设计模式

本文介绍7种实现单例设计模式的方法。

饿汉式

// final不允许被继承
public final class Singleton {

	// 实例变量
	private byte[] data = new byte[1024];
	
	// 在定义实例对象的时候直接初始化
	private static Singleton instance = new Singleton();
	
	// 私有构造方法,不允许外部new
	private Singleton() {}
	
	public static Singleton getInstance() {
		return instance;
	}
}

instance作为类变量在类初始化的过程中会被收集进()方法中,该方法能保证instance在多线程的环境下不可能被实例化两次。但是instance被ClassLoader加载后会长时间驻留在堆内存中。

懒汉式

使用的时候再去创建,避免类在初始化时提前创建。

public final class Singleton {

	private byte[] data = new byte[1024];
	
	// 定义实例,但是不直接初始化
	private static Singleton instance = null;
	
	private Singleton() {}
	
	privat static Singleton getInstance() {
		if (null == instance)
			instance = new Singleton();
		return instance;
	}
}

getInstance方法放在多线程环境下并不是安全的,并不能保证单例的唯一性。当两个线程同时看到null==instance,instance将被new两次。

懒汉式 + 同步方法

public final class Singleton {
	private byte[] data = new byte[1024];
	private static Singleton instance = null;
	
	private Singleton() {}
	
	private static synchronized Singleton getInstance() {
		if (null == instance)
			instance = new Singleton();
		return instance;
	}
}

用synchronize修饰getInstance方法,能保证instance实例的唯一性。但是由于其排他性导致了getInstance方法只能在同一时刻被一个线程所访问,性能低下。

Double-Check

public final class Singleton {
	private byte[] data = new byte[1024];
	private static Singleton instance = null;
	Connection conn;
	Socket socket;
	
	private Singleton() {
		this.conn     // 初始化conn
		this.socket   // 初始化socket
	}
	
	public static Singleton getInstance() {
		// 当instance为null时,进入同步代码块,并避免了每次都需要进入同步代码块
		if (null == instance) {
			// 只有一个线程能够获得Singleton.class关联的monitor
			synchronized (Singleton.class) {
				// 判断如果instance为null时创建
				if (null == instance) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

这种方式既满足了懒加载,有保证了instance实例的唯一性,Double-Check的方式又提供了高效的数据同步策略,可以允许多个线程同时对getInstance进行访问。但是这种方式在多线程的情况下有可能会引起空指针异常。

在Singleton的构造方法中,需要分别实例化conn和socket两个资源,还有Singleton自身。由于可能发生指令重排,可能instance最先被实例化,而conn和socket并未完成实例化。此时另一个线程判断到instance不为null,即可返回,但conn和socket还未创建完成。

Volatile + Double-Check

Double-Check方式会出现指令重排导致多线程不安全问题,可以通过volatile关键字防止重排发生。

private volatile static singleton instance = null;

Holder方式

public final class Singleton {
	private byte[] data = new byte[1024];
	
	private Singleton() {}
	
	// 在静态内部类中持有Singleton的实例,并且可被直接初始化
	private static class Holder {
		private static Singleton instance = new Singleton();
	}
	
	// 调用getInstance方法,事实上是获得Holder的instance静态属性
	public static Singleton getInstance() {
		return Holder.instance;
	}
}

在Singleton类中,将静态成员instance放到静态内部类Holder之中。因此,在Singleton类的初始化过程中不会创建Singleton实例,Holder类中定义了Singleton的静态变量,并直接进行了实例化。当Holder被主动引用时会创建Singleton的实例。创建Singleton的过程被收集到()方法中,所以该方法是同步方法,可以保证内存的可见性,指令顺序性和原子性。是目前最好的设计之一,也是被广泛使用的设计。

枚举方式

public enum Singleton {
	INSTANCE;
	private byte[] data = new byte[1024];
	
	Singleton() {
		System.out.println("INSTANCE will be initialized immediately");
	}
	
	public static void method() {
		// 调用该方法会主动使用Singleton,INSTANCE将会被实例化
	}
	
	public static Singleton getInstance() {
		return INSTANCE;
	}
}

枚举类型不允许被继承,同样是线程安全的且只能被实例化一次。但枚举类型不能够懒加载,对Singleton主动使用,比如调用其中的静态方法,则INSTANCE会立即实例化。

可以对其进行改造,采用类似Holder的方式,增加懒加载的特性。

public class Singleton {
	private byte[] data = new byte[1024];
	
	private Singleton() {}
	
	// 使用枚举充当Holder
	private enum EnumHolder {
		INSTANCE;
		private Singleton instance;
		
		EnumHolder() {
			this.instance = new Instance();
		}
		
		private Singleton getSingleton() {
			return instance;
		}
	}
	
	public static Singleton getInstance() {
		return EnumHolder.INSTANCE.getSingleton();
	}
}