八股整理xdsm
1.实现线程安全的单例模式
一、第一次检查:避免不必要的同步(提高性能)
目的:在大多数情况下,实例已经被创建了,我们不希望每次调用
getInstance()
都进入同步块,因为同步(即synchronized
)是有性能开销的。如果 instance 已经不为 null,说明单例已经创建,那么直接返回即可,不需要进入 synchronized 块,避免了线程排队等待锁,提升了性能。
二、第二次检查:确保单例只被创建一次(线程安全
进入 synchronized 块的线程可能有多个(在第一次检查都发现 instance == null 的情况下)。
第一个进入同步块的线程会创建实例,但后面其他线程可能在等待锁释放后,也进入同步块。
如果没有第二次检查,这些线程会再次创建新的实例,导致单例模式失效。
双重检查锁(Double-Checked Locking, DCL) 实现线程安全的懒汉式单例模式
public class Singleton {
// volatile 保证可见性与禁止指令重排
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查,避免不必要的同步
synchronized (Singleton.class) { // 加锁,保证线程安全
if (instance == null) { // 第二次检查,确保只创建一次
instance = new Singleton();
}
}
}
return instance;
}
}
2.怎样自定义一个类加载器
先复习一下类加载器的关键方法:
核心方法:
ClassLoader.loadClass(String name)
加载指定类名的类,默认实现遵循双亲委派机制。
该方法内部会先委派给父类加载器,父类加载不了才自己尝试加载。
ClassLoader.findClass(String name)
真正去查找并定义一个类的方法,默认实现是抛出 ClassNotFoundException。
自定义类加载器通常重写这个方法!
ClassLoader.defineClass(byte[] b, int off, int len)
将字节数组(.class 文件的内容)转换为 Class 对象,这是 JVM 提供的一个 native 方法,一般由 findClass 调用。
步骤 1:继承 ClassLoader
类
通常你只需要继承 ClassLoader
(或者 URLClassLoader
,如果你想基于路径/URL 加载),然后重写 findClass(String name)
方法
步骤 2:实现 findClass
方法
在这个方法中,你需要:
根据类名,找到对应的 .class 文件(或字节码来源:文件、网络、内存、加密文件等)
读取该文件的字节码(byte[])
调用
defineClass(name, byte[], offset, length)
方法,将字节数组转为 Class 对象
3.SPI
SPI(Service Provider Interface,服务提供者接口) 是 Java 提供的一种服务发现机制,它定义了一种 “接口与实现相分离” 的规范,允许 第三方为某个接口提供具体实现,并在运行时动态地被发现和加载。
JDBC 是 SPI 的典型应用场景 👇
1. JDBC 定义了一个标准接口
java.sql.Driver
这是由 JDK 提供的接口,它定义了连接数据库的标准方法,比如 connect()
。
但它并没有提供具体实现,比如连接 MySQL、Oracle 的代码。
2. 各数据库厂商提供实现:
比如:
MySQL 提供了
com.mysql.cj.jdbc.Driver
Oracle 提供了
oracle.jdbc.driver.OracleDriver
这些类,就是 SPI 的服务提供者实现(Service Provider)。
3. 但这些实现类不是由我们手动去 new 的,而是通过 SPI 机制自动发现和加载的!
关键点在于:MySQL 的 jar 包中有一个文件:
META-INF/services/java.sql.Driver
文件中就写了
com.mysql.cj.jdbc.Driver
它就告诉 JVM:“如果你想找一个 java.sql.Driver
的实现,可以加载我这个类”。
👉 Java 在运行时通过 ServiceLoader
找到这个配置,自动加载并实例化该驱动