定义
单例是一种设计模式,单例模式可以保证系统中只有一个类只有一个实例,而且该实例易于外界访问,从而方便实例个数的控制并节约系统资源。
单例模式通用类图
一、饿汉式
public final class Singleton{
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
类变量instance,由于通过new关键字主动始用,会在类初始化时被收集进方法,在多线程情况下不会被实例化多次,能够保证多线程环境下的唯一实例,但是如果一个类的成员占用资源比较多,使用这种方式就有所不妥,并且饿汉式单例也不能进行懒加载。
二、饿汉式变种
public final class Singleton{
private static Singleton instance = null;
static{
instance = new Singleton();
}
public static Singlton getInstance(){
return instance;
}
}
三、懒汉式
public final class Singleton{
private Singleton(){}
private static Singleton instance = null;
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
在Singleton类初始化的时候并不会导致instance实例化,在多线程环境下,会导致instance实例被实例化一次以上,不会保证单例的唯一性
四、懒汉式+同步方法
public final class Singleton{
private Singleton(){}
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
采用懒汉式+数据同步的方式,既满足了懒加载又保证了单例的唯一性,但是由于同步方法在多线程环境下会使得在同一时刻只能有同一个线程访问,导致性能比较低。
六、Double-Check
public final class Singleton{
Connection conn;
Socket socket;
private static Singleton instance = null;
private Singleton(){
this.conn;
this.socket;
}
public static Singleton getInstance(){
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton;
}
}
}
return instance;
}
}
Double-Check既满足了懒加载,又能保证实例的唯一性,提供了高效的数据同步策略,但是在多线程环境下,有可能出现空指针异常。
在Singleton的构造函数中,需要分别实例化conn和socket,还有Singleton自身,但是由于JVM在运行时的指令重排序和Happens-before规则,这三者的实例化顺序并无前后约束关系,极有可能instance最先被实例化,但是conn和socket还没有实例化。未完成实例化的实例,调用其方法将会抛出空指针异常。因此我们可以通过 private volatile static Singleton instance = null 使用volatile关键字来禁止指令重排序。
七、静态内部类
public final class Singleton{
private Singleton(){}
public static class Holder{
public static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return Holder.instance;
}
}
Singleton初始化的时候并没有创建其实例,Singleton在静态内部类中定义了实例,并且直接进行了实例化,当静态内部类主动引用的时候会被实例化,Singleton实例化的过程,在JVM程序编译器将其收集到方法中,该方法是同步方法,同步方法可以保证内存可见性,JVM指令的顺序行和原子性。
八、枚举方式(https://blog.csdn.net/moakun/article/details/80688851)
public final class Singleton{
private Singleton(){}
public enum EnumHolder{
INSTANCE;
private Singleton instance;
private EnumHolder(){
this.instance = new Singleton();
}
public static Singleton getSingleton(){
return instance;
}
}
public static Singleton getInstance(){
return EnumHolder.INSTANCE.getSingleton();
}
}
枚举单例实现方式比较精简,既能保证线程安全,又能实现懒加载,而且枚举可以避免反序列化破坏单例
我们知道普通的Java类在反序列化的过程中,回到用类的构造函数来初始化对象,所以即使单例中构造函数私有的,也会被反射给破坏掉。由于反序列化后的对象是重新new出来的,所以就破坏了单例,而枚举在在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化机制的定制的。