静态工厂模式
public class Singleton {
private Singleton(){} // 构造对外不可见,禁止在本类外创建对象
private static Singleton singleton = null; //初始值可以为null也可以为new Singleton
// 获取单例的静态方法
public static Singleton getSingleton(){
if(singleton==null)
singleton = new Singleton();
return singleton;
}
}
如果单例初始值是null,在需要时构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。适用于重量级对象的懒加载
如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。
线程安全DCL
常见错误写法如下:
public final class Singleton {
private Singleton(){}
private static Singleton singleton = null;
public static Singleton getSingleton(){
if(singleton==null)
synchronized (Singleton.class){
if(singleton==null)
singleton = new Singleton();
}
return singleton;
}
}
为什么要双锁判断?
因为我们不能在第一次判断和拿到锁的时候没有其他线程在申请对象。最简单的说,线程A可能在判断的同时恰好有线程B在拿锁,此时单例为创建为null。
而判断完毕,A拿锁的时候B创建,所以A进入同步块的时候就已经有单例创建了。
那为什么这样仍然不是线程安全的呢?
原因在于new Singleton()
不是原子的以及指令重排,在JVM中分为了3条指令
- memory =allocate(); //1:分配对象的内存空间
- ctorInstance(memory); //2:初始化对象
- instance =memory; //3:设置instance指向刚分配的内存地址
重排序后可能的顺序为
- memory =allocate(); //1:分配对象的内存空间
- instance =memory; //3:设置instance指向刚分配的内存地址
- ctorInstance(memory); //2:初始化对象
所以导致即使singleton!=null
其仍有可能为"空"。
正确的DCL如下 就是加了个volatile
public final class Singleton {
private Singleton(){}
private volatile static Singleton singleton = null;
public static Singleton getSingleton(){
if(singleton==null)
synchronized (Singleton.class){
if(singleton==null)
singleton = new Singleton();
}
return singleton;
}
}
volatile
修饰后保证完全初始化后刷入主内存,避免重排产生的半成品。关于volatile详解戳ヾ(≧O≦)〃嗷~
静态内部类
public final class Singleton {
//无法从外部访问的静态类
private static class LazyHolder{
private static final Singleton singleton = new Singleton();
}
private Singleton(){}
public static Singleton getInstance(){
return LazyHolder.singleton;
}
}
该种方式下的单例初始化在类加载时初始
枚举实现单例
关于枚举类中如何添加属性 方法,这里不做过多说明 假的链接
public enum singleton {
INSTANCE;
}
使用枚举类实现单例的好处在于可以方式通过反射的方式破坏单例。
try {
Constructor con = singleton.class.getDeclaredConstructor();
singleton s1 = (singleton)con.newInstance();
singleton s2 = (singleton)con.newInstance();
System.out.println(s1==s2);
} catch (Exception e) {
e.printStackTrace();
}
然后报错
java.lang.NoSuchMethodException: com.java.learning.singleton.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3355)
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2559)
相比于静态内部类而言,枚举类可以防止反射调用。原因在于Enum语法糖是没有默认构造器的。在反编译之后可以看到,singleton里面只有一个静态代码块,没有默认构造器
参考: