【JVM】为什么静态内部类实现单例模式是线程安全?

您所在的位置:网站首页 静态变量jvm 【JVM】为什么静态内部类实现单例模式是线程安全?

【JVM】为什么静态内部类实现单例模式是线程安全?

2023-09-04 14:03| 来源: 网络整理| 查看: 265

首先给出代码:

//基于类初始化的线程安全的单例 class SingleTon4{ private SingleTon4(){} private static class InnerClass{ private static SingleTon4 instance= new SingleTon4(); } public static SingleTon4 getInstance(){//如果没有到这里,那么不会加载上面的内部类 return InnerClass.instance; //这里将导致InstanceHolder类被初始化 } }

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。具体来说当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,使用INSTANCE的时候,才会导致虚拟机加载SingleTonHoler类。这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。

那么他是如何实现线程安全的?

首先要了解类加载过程中的最后一个阶段:即类的初始化,类的初始化阶本质就是执行类构造器的方法。

方法:这不是由程序员写的程序,而是根据代码由javac编译器生成的。它是由类里面所有的类变量的赋值动作和静态代码块组成的。JVM内部会保证一个类的方法在多线程环境下被正确的加锁同步,也就是说如果多个线程同时去进行“类的初始化”,那么只有一个线程会去执行类的方法,其他的线程都要阻塞等待,直到这个线程执行完方法。然后执行完方法后,其他线程唤醒,但是不会再进入()方法。也就是说同一个加载器下,一个类型只会初始化一次。

那么回到这个代码中,这里的静态变量的赋值操作进行编译之后实际上就是一个代码,当我们执行getInstance方法的时候,会导致SingleTonHolder类的加载,类加载的最后会执行类的初始化,但是即使在多线程情况下,这个类的初始化的代码也只会被执行一次,所以他只会有一个实例。

那么再增加一句,之所以这里变量定义的时候不需要volatile,因为只有一个线程会执行具体的类的初始化代码,也就是即使有指令重排序,因为根本没有第二个线程给你去影响,所以无所谓。

此外,如果讲得再细致一点,可以参考下面的第二个网站,简单说一下:

        比如有一个类T,那么什么时候会进行类T的初始化?有以下5种情况:

    T 是一个类,而且一个 T 类型的实例被创建;    T 是一个类,且 T 中声明的一个静态方法被调用;    T 中声明的一个静态字段被赋值;    T 中声明的一个静态字段被使用,而且这个字段不是一个常量字段;    T 是一个顶级类(top level class,见 java 语言规范的§7.6),而且一个断言语句嵌套在 T 内部被执行。

而我们前面的代码则对应了第四种情况,一个静态字段被使用,因此此时则会进行静态内部类的初始化。又因为java是多线程语言,作者也考虑到了可能存在“多个线程在同一时间尝试去初始化同一个类”的情况,因此java规定,对于每一个类或接口 C,都有一个唯一的初始化锁 LC 与之对应。

JVM 在类初始化期间会获取这个初始化锁,并且每个线程至少获取一次锁来确保这个类已经被初始化过了。(但是只有第一个获得锁的线程,会执行初始化,执行完之后会设置一个标志位,表示已经初始化完成,后面其他的线程再次获得锁,检查标志位,发现已经初始化完了,直接释放锁,不会再次执行初始化。)

 

参考:

https://www.infoq.cn/article/double-checked-locking-with-delay-initialization/(说的很好)

https://blog.csdn.net/mnb65482/article/details/80458571

《深入理解JAVA虚拟机》



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3