该静态内部类初始化方式是否存在多线程可见性问题?
初始化可见性问题结论
该写法不存在可见性问题,多线程并发调用handle()方法时,redisTemplate一定是已完成初始化的非null值,不会出现取值为null的场景。
核心逻辑依据
- JVM对类初始化阶段有原生的线程安全与可见性保证:类初始化阶段执行的
<clinit>()方法(包含静态变量赋值、静态代码块逻辑),虚拟机会自动加锁同步。多线程同时触发同一个类初始化时,仅会有一个线程执行类的<clinit>()逻辑,其余线程全部阻塞等待,直到初始化执行完毕。 - 类初始化的同步规则天然满足JMM的happens-before约定:执行类初始化的线程在释放初始化锁前,会将所有操作的写入结果刷回主存;后续阻塞等待的线程被唤醒后,会从主存读取最新的共享变量值,保证能看到初始化阶段所有赋值操作的结果。
对应代码的执行逻辑
示例代码如下:
public class InnerStaticClassExample { private static RedisTemplate<String, Object> redisTemplate; private static class Initializer { static { redisTemplate = new RedisTemplate<>(); } private static void init(){ } } /** * Multithread concurrent call */ public static void handle(String key, long time, TimeUnit timeUnit) { Initializer.init(); redisTemplate.expire(key, time, timeUnit); } }
- 当任意线程首次调用
Initializer.init()时,会首次触发Initializer类的初始化,拿到初始化锁的线程会执行静态代码块,完成redisTemplate的实例化与赋值。 - 静态代码块执行完成后
Initializer类才算初始化完成,此时等待初始化锁的其他线程才会被唤醒继续执行,所有线程都能读取到redisTemplate已完成赋值的状态。 - 类初始化只会执行一次,后续所有线程调用
Initializer.init()时,Initializer已经完成初始化,不会重复执行静态代码块,直接进入后续逻辑,不存在重复赋值或者读到未赋值状态的可能。
常见误区说明
有观点认为redisTemplate字段没有加volatile修饰,会出现指令重排序导致读到未完成初始化的对象,这个判断不适用于当前场景:
- 普通懒汉式单例在无锁、无volatile修饰下的问题,是因为没有公共同步点兜底重排序与可见性;而当前场景下的赋值动作被类初始化的锁机制完全包裹,初始化完成的同步点已经保证了所有写入结果对后续线程可见,不需要额外加volatile修饰就能保证线程安全。
- 这本质上是经典静态内部类单例模式的变体,初始化安全性由JVM层面兜底。
内容的提问来源于stack exchange,提问作者TiaJiang
相关产品推荐
相关产品推荐

