单例模式有几种写法?需要注意什么?

饱汉模式

饱汉,即已经吃饱了,不着急再吃,饿的时候再吃。
大概意思就是,他不先不初始化单例,等下一次使用的时候再初始化,即 “懒加载”。
饱汉的核心就是懒加载,好处是启动速度快,节省内存资源,到实例被获取时才需要被实例化。

1、基础的饱汉模式写法
//饱汉
public class FullSingle {

    private static MySingle mySingle;

    public static MySingle getMySingle(){
        if (null == mySingle){
            mySingle = new MySingle();
        }
        return mySingle;
    }
}

基础的写法存在线程安全的问题,大概就是多个并发执行该方法,创建的对象可能都是不同,也就是上面的if条件存在竞争。

2、Synchronized 关键字解决线程安全问题

最简单有效的方法使用 synchronized 关键字修饰 getMySingle 方法,这样能达到绝对的线程安全。

//饱汉
public class FullSingle {

    private static MySingle mySingle;

    public synchronized static MySingle getMySingle(){
        if (null == mySingle){
            mySingle = new MySingle();
        }
        return mySingle;
    }
}

这种写法虽然对线程安全有了解决,坏处就是并发的性能极差,可以认为完全退化到了串行。
对象只需创建一次,而创建完之后,后面的每次获取都避免不开 synchronized 锁,从而把这个方法变成串行的操作了。

3、DCL 解决每次都要上锁的问题 1.0 version

DCL (double check lock) 双层检查机制

//饱汉
public class FullSingle {

    private static MySingle mySingle;

     public static MySingle getMySingle(){
     if (null == mySingle){
         synchronized (FullSingle.class){
             if (null == mySingle){
                 mySingle = new MySingle();
             }
         }
     }
     return mySingle;
 	}
}

这种写法的核心是 DCL ,在 synchronized 内层又加了一个 if 判断。就是再创建完单例对象之后,可以避免再次走 synchronized 锁。

该写法存在指令重排的问题,导致对象只初始化了一半。解决方法就是 把 单例对象 修饰为 volatile ,就能禁止指令重排,被称为 DCL v2.0写法

private static volatile MySingle mySingle ;

饿汉模式

饿汉很饿,只想着尽早吃到,所以他就在最早的时机,即类加载初始化单例,后面获取的时候直接返回即可。

饿汉模式写法
1.1 基础饿汉写法
//饿汉
public class HungrySingle {

    private static MySingle mySingle = new MySingle();

    public static MySingle getMySingle(){
        return mySingle;
    }
}

饿汉的缺点就是没有懒加载,会比饱汉式浪费一些资源,启动慢一些,优点就是不会存在线程安全的问题。

单线程环境下,俄汉与饱汉在性能上没什么差别;但多线程环境下,由于饱汉需要加锁,饿汉的性能反而更优。

1.2 优化版饿汉写法 Holder 模式

我们即希望饿汉模式中的静态变量的方便和线程的安全,又希望通过懒加载避免浪费资源。Holder 模式满足了这两个优点,核心仍是静态变量,即方便又保证了线程安全,通过静态内部类持有真正的实例,间接的使用了懒加载。

//饿汉
//Holder模式
public class HungrySingle {

    private static class HungrySingleHolder{

        private static final MySingle mySingle = new MySingle();

        private HungrySingleHolder(){

        }
    }

    private HungrySingle() {
    }
    
    public static MySingle getMySingle(){
       return HungrySingleHolder.mySingle;
    }
}

相对于基础的饿汉模式写法,Holder 模型增加了静态内部类,性能效果和饱汉的两层校验的写法相当(略优)。

1.3 枚举模式写法

用枚举模式实现单例比较方便,但不存在可读性。

基础枚举实现单例

//将枚举的静态变量作为单例的实例
public enum SingleEnum {
    SINGLE;
}

通过反编译打开枚举,就看到了枚举类型的本质,简化如下:

//枚举
public class SingleEnum extends Enum<SingleEnum> {
    ...
    public static final SingleEnum SINGLE = new SingleEnum;
    ...
}
总结

上面考虑的场景,都忽略了反射和序列化的情况。通过反射和序列化能够访问到私有构造器,这样就能破坏单例对象。只有枚举模式能天然防范这一问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赖伟春

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值