如何确保静态变量初始化后立即被所有线程缓存,避免重复初始化?
我有大量线程会使用一个静态变量,该变量在同步块中进行懒加载初始化。由于初始化操作占用大量内存,因此理想状态下仅需执行一次。我了解线程会访问静态变量的私有缓存副本,但缓存需要一定时间,导致初始阶段所有线程都会进入初始化块。即便有同步机制,这仍可能引发内存溢出错误。
请问是否存在方法,能让该静态变量在被第一个线程初始化后,立即对所有新线程可用?
简化版代码:
class TestingThread extends Thread { private static Templates transformTemplateRequest = null; private static final Object lockObj = new Object(); private Document xslt; public void run() { // Document xslt initialized Transformer requestTransformer = null; synchronized (lockObj) { if (transformTemplateRequest == null) { TransformerFactory tf = TransformerFactory.newInstance(); Source xsltSource = new DOMSource(xslt); transformTemplateRequest = tf.newTemplates(xsltSource); } } requestTransformer = transformTemplateRequest.newTransformer(); // other work } }
你的核心痛点其实是两个:一是可见性(确保初始化后的变量能立刻被所有线程感知),二是避免大量线程阻塞在同步块加剧内存压力。当前代码虽然能保证初始化只执行一次,但同步块会让所有后续线程排队等待,而且没有显式处理变量的可见性问题,确实存在优化空间。下面给你几个实用的解决方案:
方案1:volatile+双重检查锁定(DCL)
这是最经典的懒加载优化方案,既能保证可见性,又能让大部分线程跳过同步块直接读取变量:
class TestingThread extends Thread { // 加volatile:禁止指令重排序+强制变量读写刷新到主内存,确保所有线程看到最新值 private static volatile Templates transformTemplateRequest = null; private static final Object lockObj = new Object(); private Document xslt; public void run() { // Document xslt initialized Transformer requestTransformer = null; // 第一次无锁检查:大部分线程直接拿到已初始化的值,不用进同步块 if (transformTemplateRequest == null) { synchronized (lockObj) { // 第二次加锁检查:确保只有第一个线程执行初始化 if (transformTemplateRequest == null) { TransformerFactory tf = TransformerFactory.newInstance(); Source xsltSource = new DOMSource(xslt); transformTemplateRequest = tf.newTemplates(xsltSource); } } } requestTransformer = transformTemplateRequest.newTransformer(); // other work } }
方案2:静态内部类实现懒加载(Bill Pugh单例模式)
利用Java类加载机制的天然线程安全性——静态内部类只会在第一次被引用时加载,而且类加载过程是线程安全的,完全不需要手动加锁,是最简洁安全的方案:
class TestingThread extends Thread { // 注意:如果所有线程共用同一个xslt,这里要改成静态变量,否则每个线程的模板会不一致 private static Document xslt; private Document threadXslt; // 如果每个线程有独立xslt,用这个实例变量 // 静态内部类:只有当getTemplate()被调用时才会加载 private static class TemplateHolder { // 类加载时自动初始化,天然线程安全+可见性 private static final Templates transformTemplateRequest = initTemplate(); private static Templates initTemplate() { TransformerFactory tf = TransformerFactory.newInstance(); Source xsltSource = new DOMSource(xslt); // 全局共用xslt // 如果是每个线程独立xslt,这个方案不适用,得重新设计变量作用域 return tf.newTemplates(xsltSource); } } public void run() { // Document threadXslt initialized(如果是线程独立xslt) Transformer requestTransformer = null; // 直接获取内部类的静态变量,类加载机制保证一切安全 requestTransformer = TemplateHolder.transformTemplateRequest.newTransformer(); // other work } }
这里要特别提醒:你的代码里xslt是实例变量,如果每个线程的xslt不一样,那用静态模板变量本身就不合理——所有线程会共用同一个模板,这可能不符合你的需求。如果是这种情况,你需要把模板变量改成实例变量,或者调整初始化逻辑。
方案3:无锁CAS实现(AtomicReference)
如果对性能要求极高,且能接受极小概率的重复初始化(重复创建的实例会被丢弃),可以用AtomicReference的CAS操作实现无锁懒加载:
import java.util.concurrent.atomic.AtomicReference; class TestingThread extends Thread { private static final AtomicReference<Templates> transformTemplateRequest = new AtomicReference<>(null); private Document xslt; public void run() { // Document xslt initialized Transformer requestTransformer = null; Templates template = transformTemplateRequest.get(); if (template == null) { // 先创建实例 TransformerFactory tf = TransformerFactory.newInstance(); Source xsltSource = new DOMSource(xslt); Templates newTemplate = tf.newTemplates(xsltSource); // 原子性设置:只有当当前值还是null时才会设置成功 if (transformTemplateRequest.compareAndSet(null, newTemplate)) { template = newTemplate; } else { // 其他线程已经初始化完成,直接拿现成的 template = transformTemplateRequest.get(); } } requestTransformer = template.newTransformer(); // other work } }
这种方式没有锁竞争,但如果初始化成本极高,可能会有多个线程同时创建实例(只有第一个成功的会被保留),会浪费一些内存,所以适合初始化成本不太高的场景。
总结一下:如果是全局共用模板,优先选静态内部类方案,简洁又安全;如果不能调整类结构,选volatile+双重检查锁定;性能敏感且初始化成本低的场景可以用CAS无锁方案。
内容的提问来源于stack exchange,提问作者ACHC

