看似简单并不简单的单例模式

引言

今天无意间在公众号上看到一篇文章,文章中问到了一个问题,如何在不使用 synchronized 和 Lock 锁的情况下,实现单例模式?说实话,在此之前,只知道单例模式的两种实现模式,其他的压根见都没见过。今天算是开了眼界了,还是才学疏浅,路还很长啊!那就借此机会,趁热总结。

常见的单例模式

基于volatile的解决方案

这种模式其实就是我们俗称的懒汉模式,也叫双重检查锁定。在多线程环境中,为了保证类初始化只被初始化一次,就需要使用锁互斥保证原子性。为什么要加两层锁呢?这是为了防止发生竟态条件问题,再就是由于编译优化会带来有序性问题。这种模式也很容易理解,以下给出代码:

/*
    基于volatile的解决方案
 */
public class DoubleCheckedLocking {
    //volatile保证实例原子性
    private static volatile Instance instance;

    public static Instance getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (instance == null) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

class Instance { }

延迟初始化

接下来就是与之对应的饿汉模式,也叫延迟初始化。这种模式也很好理解,借助class的类加载机制实现线程安全单例。

public class Singleton { 
    private static Singleton instance = new Singleton();
    private Singleton (){}
    public static Singleton getInstance() {
      return instance;
    }
}

这种方案还有一种变种形式:

public class Singleton {
    private Singleton instance = null;
    static {
        instance = new Singleton();
    }
    private Singleton (){}
    public static Singleton getInstance() {
        return this.instance;
    }
}

此外,还有更高级一点的写法,使用静态内部类:

/*
    基于类初始化的解决方案
 */
public class InstanceFactory {
    private static class InstanceHolder {
        public static Instance instance = new Instance();
    }

    public static Instance getInstance() {
        return InstanceHolder.instance;
    }
}

这种方式相比前面两种有所优化,就是使用了 lazy-loading 。 Instance 类被加载了,但是 instance 并没有立即被初始化。因为 InstanceHolder 类并没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 InstanceHolder 类,从而实例化 instance 。

CAS单例模式

以上就是最基本的单例模式实现方式,接下来就是利用我们熟知的CAS机制实现乐观锁的单例模式:

/*
    CAS实现单例模式
 */
public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();

    private Singleton() {}

    public static Singleton getInstance() {
        for (; ; ) {
            Singleton singleton = INSTANCE.get();
            if (singleton != null) {
                return singleton;
            }

            singleton = new Singleton();
            if (INSTANCE.compareAndSet(null, singleton)) {
                return singleton;
            }
        }
    }
}

用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。
CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。

另外,如果N个线程同时执行到 singleton = new Singleton(); 的时候,会有大量对象创建,很可能导致内存溢出所以,不建议使用这种实现方式。

使用枚举类实现单例模式

重头戏来了,这可以是大名鼎鼎的《Effective Java》中推荐的单例模式实现方法!因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现 Singleton 的最佳方法。

看源码:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {}

可以看出它实现了 Comparable 和 Serializable 接口,其实 Enum 就是一个普通的类,它继承自 java.lang.Enum 类。在JDK5 中提供了大量的语法糖,枚举就是其中一种。枚举也是语法糖的一种。
所谓语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是但是更方便程序员使用。只是在编译器上做了手脚,却没有提供对应的指令集来处理它。

用枚举类实现单例模式非常简单:

public enum Singleton{
    INSTANCE;
    
    public void whateverMethod(){}
}

我们来用模拟下枚举类实现数据库连接场景:

public enum SingletonOfEnum {
    DATASOURCE;
    private MYSQLConnection connection = null;

    private SingletonOfEnum() {
        connection = new MYSQLConnection();
    }

    public MYSQLConnection getConnection() {
        return connection;
    }

}

class MYSQLConnection{}

主方法:

public class Main {
    public static void main(String[] args) {
        MYSQLConnection c1 = SingletonOfEnum.DATASOURCE.getConnection();
        MYSQLConnection c2 = SingletonOfEnum.DATASOURCE.getConnection();
        MYSQLConnection c3 = SingletonOfEnum.DATASOURCE.getConnection();

        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
        System.out.println(c2 == c3);
    }
}

输出结果:

由此可见,三次返回的都是同一个实例!

那么枚举类是怎么在多线程情况下实现线程安全的?

反编译代码:

public final class DesignPattern.SingletonPattern.EnumSingleton.SingletonOfEnum extends java.lang.Enum<DesignPattern.SingletonPattern.EnumSingleton.SingletonOfEnum> {
  public static final DesignPattern.SingletonPattern.EnumSingleton.SingletonOfEnum DATASOURCE;

由反编译后的代码可知,DATASOURCE 被声明为 static 的,根据饿汉模式中所描述的类加载过程,可以知道虚拟机会保证一个类的 <clinit>() 方法 在多线程环境中被正确的加锁、同步。所以,枚举实现是在实例化时是线程安全。

那么序列化是怎么回事呢?

如果看Enum源码,会发现有一个valueOf() 方法:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
}

Java规范中规定,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。
在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过 java.lang.Enum 的 valueOf() 方法来根据名字查找枚举对象。
也就是说,以下面枚举为例,序列化的时候只将 DATASOURCE 这个名称输出,反序列化的时候再通过这个名称,查找对于的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

由此可得出结论:枚举类本身就保证序列化单例。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值