前言:
最近在看《Java并发编程实践》,里面提到了一种实现单例模式的方式,并大致说明了机制,但仍不是很清晰,今日有空,查阅相关书籍,尝试解释其中道理。
单例模式:
单例模式是一种常用的软件设计模式,它用于确保一个类只有一个实例,并提供一个全局访问点。单例模式通常用于需要频繁实例化且实例化对象消耗较大的情况,例如数据库连接、线程池等。
单例模式的实现方式有多种,包括饿汉式、懒汉式、双重校验锁、静态内部类等。其中饿汉式是指在类加载时就已经实例化对象,懒汉式则是延迟实例化,双重校验锁则是利用volatile关键字和synchronized关键字来保证线程安全。
单例模式的优点是可以避免对资源的多重占用,减少内存开销,同时设置全局访问点,可以优化和共享资源的访问。但是单例模式也存在一些缺点,例如如果要扩展该单例类,除了修改原来的代码,没有第二种途径,违背开闭原则。
总之,单例模式是一种常用的软件设计模式,它可以提高程序的性能和效率,但需要注意线程安全和扩展性问题。
以上来自文心一言。
单例的实现:
一般有懒汉,饿汉,DCL(双检锁),枚举,还有本文的holder,详细对应代码,可自行网上查阅。
类加载时机:
关于在什么情况下需要开始类加载过程的第一个阶段“加载”,《Java虚拟机规范》中并没有进行 强制约束,这点可以交给虚拟机的具体实现来自由把握。但是对于初始化阶段,《Java虚拟机规范》 则是严格规定了有且只有六种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始):
1)遇到new、getstatic、putstatic或invokestatic这四条字节码指令时,如果类型没有进行过初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有:
·使用new关键字实例化对象的时候。
·读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外) 的时候。
·调用一个类型的静态方法的时候。
2)使用java.lang.reflect包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需 要先触发其初始化。
3)当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先 初始化这个主类。
5)当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
6)当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有 这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
-- 来自《深入理解Java虚拟机:JVM高级特性与最佳实践》
如果觉得难以理解以上,还可以参考下R大的回答:
书中的代码形式及解释:
书中代码:
由上文可知,在调用ResourceFactory的getResource方法时,首先会加载和初始化本身(调用一个类型的静态方法的时候),然后会触发ResourceHolder类加载和初始化(读取或设置一个类型的静态字段),再触发Resouce类的加载和初始化(使用new关键字实例化对象的时候)。
书中的解释如下:
日常写Holder类的形式及解释:
日常写单例的时候可以下面这种方式:
public class Student{
private Student() {
}
private static class Holder{
public static Student stu = new Student();
}
public static Student getInstance() {
return Holder.stu;
}
}
Resource相当于Student,在书的例子中:当使用Student类的getInstance方法时,首先会触发Student的加载机制(静态方法),然后会触发Holder内部类的加载(静态变量)。由于Student类已经被加载过了,所以不用再次加载。