静态内部类为何能够实现单例?

Posted 小猪快跑22

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了静态内部类为何能够实现单例?相关的知识,希望对你有一定的参考价值。

public class DeviceHelper 

    private DeviceHelper() 

    

    private static class DeviceHelperHolder 
        /**
         * jvm 虚拟机规定当主动 new 一个对象时会触发类的初始化,初始化阶段也就是执行类构造器 <clinit>()方法的过程;
         * <clinit>() 方法对于类来说不是必须的,如果一个类中既没有静态语句块也没有静态变量赋值动作,
         * 那么编译器都不会为类生成<clinit>()方法。
         *
         * 虚拟机会保证一个类的 <clinit>() 方法在多线程环境中能被正确的加锁、同步。如果多个线程初始化一个类,
         * 那么只有一个线程会去执行<clinit>()方法,其它线程都需要等待。
         *
         *
         * 下面的 instance 是属于静态变量赋值,那么会执行类构造器 <clinit>() 方法,从而保证线程安全。
         *
         */
        private static DeviceHelper instance = new DeviceHelper();
    

    public static DeviceHelper getInstance() 
        // jvm 规定读取一个类的静态属性(非静态常量 final static int a =1 这种),会触发类的初始化。
       // 如下会触发 DeviceHelperHolder 类的初始化
        return DeviceHelperHolder.instance;
    

上面解释了原因,静态内部类的方式有个不足之处就是不能传递参数,比如你需要在 DeviceHelperHolder 中使用 Context,那么无法通过构造函数传递。

优势也很明显:

  1. 类似于饿汉模式,用到才初始化。
  2. 由JVM保证并发安全,性能比使用同步锁要高。

触发类的初始化的5种场景:

  1. new 一个类
  2. 读取或设置一个类的静态字段(static int a = 10)且该字段不是 final修饰的(因为 static final 字段在类的准备阶段已经赋值)或者调用静态方法
  3. 使用反射的方法对类进行反射调用时,如果该类未初始化则进行初始化
  4. 当初始化一个类时,如果其父类还未初始化,则先初始化其父类
  5. 当Java虚拟机启动时,初始化包含 main 方法的主类

对象的初始化顺序如下:

静态变量/静态代码块 -> 普通变量/普通代码块 -> 构造函数

  1. 父类静态变量和静态代码块;
  2. 子类静态变量和静态代码块;
  3. 父类普通成员变量和普通代码块;
  4. 父类的构造函数;
  5. 子类普通成员变量和普通代码块;
  6. 子类的构造函数。

以上是关于静态内部类为何能够实现单例?的主要内容,如果未能解决你的问题,请参考以下文章

Java 设计模式 -- 单例模式的实现(饿汉式枚举饿汉式懒汉式双检锁懒汉式内部类懒汉式)jdk 中用到单例模式的场景DCL实现单例需用volatile 修饰静态变量

Java 设计模式 -- 单例模式的实现(饿汉式枚举饿汉式懒汉式双检锁懒汉式内部类懒汉式)jdk 中用到单例模式的场景DCL实现单例需用volatile 修饰静态变量

静态内部类实现单例模式

使用静态内部类实现单例设计模式

使用静态内部类实现单例设计模式

单例模式 静态库和动态库的区别