记JAVA单例模式的几种写法

      单例模式是应用最广的设计模式之一,也是写法和变种最多的一种设计模式。单例对象要确保在全局中只存在一个。这种模式适合消耗资源严重的使用场景。

      实现单例模式主要注意以下几个关键点:

1)构造函数不对外开放,一般为private;

2)通过一个静态方法或枚举返回单例类对象;

3)确保单例类的对象有且只有一个,尤其在多线程的环境下;

4)确保单例类对象在反序列化时不会重新构建对象。

下面通过代码演示单例模式的几种写法:

饿汉式

废话不说,直接上代码

//饿汉式单例
public class SingleTon {
    private static SingleTon singleTon=new SingleTon();
    private SingleTon(){
        
    }
    public static SingleTon getInstance(){
        return singleTon;
    }
}

这是最简单的单例模式的实现。这种方式创建的对象,在声明对象的时候就已经初始化,但是如果程序运行过程中没有使用到,就白白浪费了内存。为了弥补这样的缺陷,就产生了懒汉式单例模式。

懒汉式
//懒汉式单例
public class SingleTon {
    private static SingleTon singleTon;
    private SingleTon(){

    }
    public synchronized static SingleTon getInstance(){
        if (singleTon==null){
            singleTon=new SingleTon();
        }
        return singleTon;
    }
}

这种写法保证了在第一次调用getInstance()才对singleTon实例进行初始化。在一定程度上节约了资源;但是在第一次加载的时候需要进行实例化,反应会变慢,还有就是每次调用getInstance都会进行同步,造成了不必要的锁开销。

双重检查锁(DCL)

     如果既要保证在需要时才初始化单例,又要保证线程安全,同时单例对象初始化后调用getInstance()不进行同步锁,那么就产生了DCL单例

//DCL单例
public class SingleTon {
    private static volatile SingleTon singleTon;

    private SingleTon() {

}

    public static SingleTon getInstance() {
        if (singleTon == null) {
            synchronized (SingleTon.class) {
                if (singleTon == null) {
                    singleTon = new SingleTon();
                }
            }
        }
        return singleTon;
    }
}

上面这段代码中,getInstance()方法做了2次判空,第一次判空主要是考虑到singleTon在已经加载过的情况下避免不必要的同步,第二层判断是考虑到singleTon在null的情况下创建实例。这里为了保证singleTon = new SingleTon()能够被同步执行,在外层添加了synchronized锁。但是Java编译器允许处理器可以乱序执行,会导致getInstance()没有在线程A上执行完,而被切换到线程B上,线程B直接取走singleTon造成使用出错,这就是DCL失效问题,为了解决这个问题,在JDK1.5以后,Java官方调整了JMM,修正了volatile关键字的语义,只要将singleTon设置成volatile属性,就可以保证singleTon对象每次都是从主内存中读取,保证了DCL的正确性。

DCL模式能够在绝大多数场景下保证单例对象的唯一性,也弥补了以上2种单例模式的缺陷。是使用最多的单例模式。

静态内部类

DCL在一定程度上解决了资源消耗,多余的同步,线程安全等问题,但是在JDK1.5以下还会出现DCL失效的问题。而且代码对于初级工程师来说比较难懂,所以出现了静态内部类的单例模式

//静态内部类
public class SingleTon {
    private SingleTon() {
    }

    private static class SingletonHolder{
        private static SingleTon singleTon=new SingleTon();
    }
    public static SingleTon getInstance(){
        return SingletonHolder.singleTon;
    }
}

当第一次加载SingleTon类的时候并不会初始化singleTon,只有在第一次调用getInstance()的时候才会初始化singleTon实例。第一次调用getInstance()方法会导致虚拟机加载SingletonHolder类,这样不仅保证了线程安全,也保证了单例的唯一性和延迟加载特性。

枚举

上述的几种单例实现都会在一个情况下重新创建对象实例,那就是反序列化。反序列化时通过特殊的途径去创建类的一个新的实例,相当于调用了该类的构造函数。反序列化操作提供了一个特别的回调函数,即一个私有、被实例化的方法readResolve(),这个方法可以让开发人员控制对象的反序列化。

private Object readResolve() throws ObjectStreamException{
    // 此处返回单例模式中的实例对象
    return singleTon;
}

这样在readResolve方法中返回当前实例,而不是重新生成对象,在反序列化时也能保证单例。

还有一种方式,也不存在反序列化的问题,那就是通过枚举。默认枚举创建的实例是线程安全的,并且在任何情况下都是单例。

//枚举
public enum SingletonEnum{
    SINGLETON;
    public void method(){
        //do something...
    }
}
除了上面的几种单例创建方式,还有其他的一些单例写法,比如静态代码块,但是原理都包含在上面的几种写法里。单例模式的核心原理都是将构造函数私有化,并通过静态方法获得唯一的实例。并且根据项目需要选择适合的单例方式。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值