1 饿汉式
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
作为类变量并且直接得到了初始化,如果主动使用Singleton
类,那么instance
实例将会直接完成创建,包括其中的实例变量都会得到初始化,比如1K
空间的data
将会同时被创建。
但是instance
被ClassLoader
加载后可能很长一段时间才被使用,那就意味着instance实例所开辟的堆内存会驻留更久的时间。如果一个类中的成员属性比较少,且占用的内存资源不多,饿汉的方式也未尝不可,相反,如果一个类中的成员都是比较重的资源,那么这种方式就会有些不妥。
总结起来,饿汉式的单例设计模式可以保证多个线程下的唯一实例,getInstance方法性能也比较高,但是无法进行懒加载。
2 懒汉式
public final class Singleton {
// 实际变量
private byte[] data = new byte[1024];
// 在定义实例变量的时候直接初始化
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
Singleton
的类变量instance=null
,因此当Singleton.class
被初始化的时候instance
并不会被实例化,在getInstance
方法中会判断instance
实例是否被实例化,看起来没有什么问题,但是将getInstance方法放在多线程环境下进行分析,线程不安全
如图所示,两个线程同时看到instance==null
,那么instance
将无法保证单例的唯一性。
3 懒汉式+同步方法
public final class Singleton {
// 实际变量
private byte[] data = new byte[1024];
// 在定义实例变量的时候直接初始化
private static Singleton instance = null;
private Singleton() {}
// 向getInstance方法加入同步控制,每次只能有一个线程能够进入
public static synchronized Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
}
采用懒汉式+数据同步的方式既满足了懒加载又能够百分之百地保证instance
实例的唯一性,但是**synchronized
关键字天生的排他性导致了getInstance
方法只能在同一时刻被一个线程所访问,性能低下。**
4 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初始化
// this.socket初始化
}
public static Singleton getInstance() {
// 当instance为null时,进入同步代码块
//同时该判断避免了每次都需要进入同步代码块,可以提高效率
if (null == instance) {
synchronized (Singleton.class) {
// 判断如果instance为Null时则创建
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
当两个线程发现null==instance
成立时,只有一个线程有资格进入同步代码块,完成对instance
的实例化,随后的线程发现null==instance
不成立则无须进行任何动作,以后对getInstance
的访问就不需要数据同步的保护了。
这种方式看起来是那么的完美和巧妙,既满足了懒加载,又保证了instance
实例的唯一性,Double-Check
的方式提供了高效的数据同步策略,可以允许多个线程同时对getInstance
进行访问,但是这种方式在多线程的情况下有可能会引起空指针异常
在Singleton
的构造函数中,需要分别实例化conn
和socket
两个资源,还有Singleton
自身,根据JVM
运行时指令重排序和Happens-Before
规则,这三者之间的实例化顺序并无前后关系的约束,那么极有可能是 instance
最先被实例化,而conn
和socket
并未完成实例化,未完成初始化的实例调用其方法将会抛出空指针异常
5 Volatile + Double-Check
Double-Check
虽然是一种巧妙的程序设计,但是有可能会引起类成员变量的实例化conn
和socket
发生在 instance
实例化之后,这一切均是由于JVM
在运行时指令重排序所导致的,而volatile
关键字则可以防止这种重排序的发生,因此代码稍作修改即可满足多线程下的单例、懒加载以及获取实例的高效性,代码修改如下:
private volatile static Singleton instance = null;
6 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
实例的创建过程在Java
程序编译时期收集至<clinit>()
方法中,该方法又是同步方法,同步方法可以保证内存的可见性、JVM
指令的顺序性和原子性、Holder
方式的单例设计是最好的设计之一,也是目前使用比较广的设计之一。
7 枚举方法
使用枚举的方式实现单例模式是《Effective Java》
作者力推的方式,在很多优秀的开源代码中经常可以看到使用枚举方式实现单例模式的(身影),枚举类型不允许被继承,同样是线程安全的且只能被实例化一次,但是枚举类型不能够懒加载,对Singleton
主动使用,比如调用其中的静态方法则INSTANC
E会立即得到实例化
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将会被实例化
System.out.println("run method");
}
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
Singleton.method();
}
}
/*
INSTANCE will be initialized immediately
run method
*/
7.1 增加懒加载特性
public class Singleton {
private byte[] data = new byte[1024];
private Singleton() { }
// 使用枚举充当holder
private enum EnumHolder {
INSTANCE;
private Singleton instance;
EnumHolder() {
this.instance = new Singleton();
}
private Singleton getSingleton() {
return instance;
}
}
public static Singleton getInstance() {
return EnumHolder.INSTANCE.getSingleton();
}
}