单例模式

静态工厂模式

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里面只有一个静态代码块,没有默认构造器


参考:

  1. 漫画:什么是单例模式? - 小灰的文章 - 知乎
  2. java枚举类型的实现原理
  3. “双重检验锁失效”的问题说明
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值