liut

liut.xyz

从 Java 类的初始化看待饿汉单例

Posted at — Aug 1, 2019

先说观点: 在一般情况下, 饿汉式单例也不一定是立即加载的, 没必要特意使用写法复杂并且可能效率不高的懒汉模式。

单例模式

Java中单例 (Singleton) 模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。

分类以及常见写法

根据单例的初始化时机, java 单例可分为两大类别, 懒汉式饿汉式。 (此外, 还有登记式, 不在本文讨论范围内)

饿汉式

饿汉式是在类加载器初始化该单例类时进行实例化。 常见方式如下:

懒汉式

从 Java 类的初始化看待饿汉单例

在Java中, 静态的属性以及静态代码块, 是在类的初始化时进行加载的。 类被类加载器加载后, 并不会立即执行初始化的, 而是在特定时机才会去初始化, 具体的时机, jvm有相关规范, 大致如下:

从以上几点, 可以得出, 一个类, 在真正用到它时才会进行初始化, 才会去初始化(实例化)其静态属性和执行其静态代码快。 也就是说, 在一般情况下, 饿汉模式(本文中的静态工厂方法式饿汉和模具式单例), 对象的实例化的时候, 一般是在首次用到它的时候! 也就是说, 它是懒加载的

当然, 只是一般情况下是如此, 也有例外, 如果项目中有骚操作会反射该单例类, 也会引起初始化。 另外不要在该类中定义其它静态属性(不包括基础数据类型常量和String类型常量, 它们不会引起类的初始化)和方法, 避免首次使用该类时是使用它们导致的初始化。

kotlin 对象声明

kotlin 官方文档推荐的单例模式:对象声明本质上也是常规的饿汉模式

一个对象声明式单例

object ObjClassTest {

    init {
        println("haha init")
    }


    fun test() {
        println("hehe test")
    }

}

编译再反编译成Java代码

@Metadata(bv = {1, 0, 3}, d1 = {"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0003\u001a\u00020\u0004¨\u0006\u0005"}, d2 = {"Lxyz/liut/kotlin/lang/kt1_object_class/comm/ObjClassTest;", "", "()V", "test", "", "JavaCode"}, k = 1, mv = {1, 1, 15})
/* compiled from: ObjClassTest.kt */
public final class ObjClassTest {
    public static final ObjClassTest INSTANCE = new ObjClassTest();

    static {
        System.out.println("haha init");
    }

    private ObjClassTest() {
    }

    public final void test() {
        System.out.println("hehe test");
    }
}

在一般情况下, 饿汉模式是最好的单例方式, 写法简单、 性能好、 线程安全。

comments powered by Disqus