一、饿汉模式
public final class HungrySingleton {
//实例变量
private byte[] data = new byte[1024];
//在定义实例对象的时候直接初始化
private static HungrySingleton instance = new HungrySingleton();
//定义私有构造函数,不允许外部 new
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
说明:
饿汉模式的关键在于instance作为类变量摒弃直接得到了初始化,在instance作为类变量在初始化的过程中会被收集近<cinit>()方法中,该方法能够百分之百的保证同步,也就是说instance在多线程的情况下不能被实例化两次,但是instance被ClassLoader加载后可能很长一段时间才被使用,那就意味着instance实例所开辟的内存会驻留更久的时间。
如果一个类的成员变量属性比较少,且占用的内存资源不多,饿汉模式也未尝不可,如果一个类的成员都是比较中的资源,那么这种模式就不适宜了。
二、懒汉模式
public final class LazySingleton {
//实例变量
private byte[] data = new byte[1024];
private static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if(null == instance){
instance = new LazySingleton();
}
return instance;
}
}
说明:
由于类变量instance = null,因此当LazySingleton.class被初始化的时候,instance不会被实例化,在getInstance方法中判断instance实例是否被实例化,在单线程下是没有问题的,但是在多线程情况下进行分析,则会导致instance被实例化多次,并不能保证单例的唯一性。
三、懒汉式 + 同步方法
public final class LazySynchSingleton {
//实例变量
private byte[] data = new byte[1024];
private static LazySynchSingleton instance = null;
private LazySynchSingleton(){}
public static synchronized LazySynchSingleton getInstance(){
if(null == instance){
instance = new LazySynchSingleton();
}
return instance;
}
}
说明:
此种模式通过synchronized同步方法可以满足了懒加载又能够百分之百的保证instance实例的唯一性,但是synchronized关键字天生的排他性导致了getInstance方法只能在统一时刻被一个线程访问,性能低下。
四、双重检查模式
public final class DoubleCheckSingleton {
//实例变量
private byte[] data = new byte[1024];
private static DoubleCheckSingleton instance = null;
private DoubleCheckSingleton(){}
public static DoubleCheckSingleton getInstance(){
if(null == instance){ // (1)
synchronized (DoubleCheckSingleton.class){ //(2)
if(null == instance){ // (3)
instance = new DoubleCheckSingleton(); // (4)
}
}
}
return instance;
}
}
说明:
当两个两个线程发现 (1)处的null == instance成立时,只有一个线程有资格进入(2)处的同步代码块,完成对instance的实例化,随后的线程发现 null == instance不成立则无须进行任何动作,以后对getInstance的访问就不需要数据同步的保护了。
但是这种模式下虽然看似既满足了懒加载,有保证了instance实例的 唯一性,双重检查的方法又提供了高效的数据同步策略,可以允许多个线程同时对getInstance进行访问,但是这种方式在多线程情况下可能会引起空指针异常。
分析如下:
假设(4)处的对象实例化分解为如下3行代码
memory = allocate(); // 1: 分配对象的内存空间
ctorInstance(memory); // 2: 初始化对象
instance = memory; // 3: 设置instance指向刚分配的内存地址
根据在编译器和处理器进行执行时重排序优化,可能存在对 2、3之间发生重排序情况,如果发生重排序,第一个线程还没有初始化完成,第二个线程判断(1)处null == instance不成立,直接返回,可能会存在空指针异常的风险。
五、volatile + 双重检查模式
该模式跟双重检查模式的唯一区别就是在定义instance变量前加上 volatile 关键字修饰
private static volatile DoubleCheckSingleton instance = null;
根据volatile的内存语义加上此关键字后,则不会出现指令重排序的情况,即 在 “四”中提到的 2、3指令将不会发生重排序。因此该模式既可以满足多线程下的单例、懒加载以及获取实例的高效性。
六、Holder模式(基于类初始的模式)
public final class HolderSingleton {
//实例变量
private byte[] data = new byte[1024];
private HolderSingleton(){}
private static class Holder{
private static HolderSingleton instance = new HolderSingleton();
}
public static HolderSingleton getInstance(){
return Holder.instance;
}
}
该模式中没有在HolderSingleton中定义instance的静态变量,而是将其放到静态内部类Holder之中,因此在HolderSingleton初始化过程中并不会创建HolderSingleton的实例,Holder中定义了HolderSingleton的静态变量,并且进行了实例化,当Holder被主动引用的时候则会创建HolderSingleton实例,HolderSingleton实例的创建过程在java编译期收集值<cinit>()方法中,该方法又是同步方法,同步方法可以保证内存的可见性、JVM指令的顺序性和原子性,Holder模式的单例是最好的设计之一,也是目前使用较广的设计之一。
扩展知识
初始化一个类,包括执行这个类的静态初始化和初始化这个类中的静态字段。根据Java语言规范,在首次发生下列人员一种情况时,一个类或接口类型T将被立即初始化
1)T是一个类,而且一个T类型的实例被创建。
2)T是一个类,且T中声明的一个静态方法被调用。
3)T中声明的一个静态字段被赋值。
4)T中声明的一个静态字段被使用,而且这个字段不是一个常量字段。
5)T是一个顶级类(Top Level Class,见Java语言规范的§7.6),而且一个断言语句嵌套在T内部被执行
七、枚举方式
public final class EnumSingleton {
private byte[] data = new byte[1024];
private EnumSingleton(){}
private enum EnumHolder {
INSTANCE;
private EnumSingleton instance;
EnumHolder() {
this.instance = new EnumSingleton();
}
private EnumSingleton getInstance(){
return instance;
}
}
public static EnumSingleton getInstance(){
return EnumHolder.INSTANCE.getInstance();
}
}
该方式实现的单例模式是《Effective Java》作者力推的方式,在很多优秀的开源代码找中经常可以看见使用枚举方式实现单例模式的身影,枚举类型不允许继承,同时又是线程安全的且只能被实例化一次,但是枚举类型不能够懒加载,对Signleton主动使用,比如调用其中的静态方法则INSTANCE会立即得到实例化。
网友评论