You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot多线程场景下@Cacheable不生效如何解决

核心问题定位

@Cacheable基于Spring AOP代理实现,只有调用Spring容器管理的代理Bean上的方法时,缓存切面才会生效。当前代码中ThreadExample是手动通过new关键字创建的,不归Spring容器管理:

  • 类内@Autowired标记的studentService无法完成注入,实际为null,调用时甚至会抛出空指针
  • 就算手动给studentService赋值,若赋值的不是Spring生成的代理实例,缓存逻辑也不会触发
  • 默认配置下多线程并发请求同一个未缓存的key时,会出现缓存击穿,导致方法被多次执行,不符合预期。

可行实现方案

方案1:最小改动 - 手动获取Spring容器中的代理Bean

不需要调整线程创建逻辑,只需要实现一个Spring上下文工具类,在线程执行逻辑中直接获取容器中已经被AOP增强的StudentService实例即可。

  1. 定义上下文工具类:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}
  1. 修改ThreadExample的执行逻辑,移除成员变量注入,直接在run方法中获取Bean:
public class ThreadExample extends Thread {
    @Override
    public void run() {
        StudentService studentService = SpringContextUtil.getBean(StudentService.class);
        studentService.getStudentByID("3", "f");
    }
}

原有线程启动代码不需要任何改动。

方案2:规范实现 - 将任务纳入Spring容器管理

手动new线程本身存在频繁创建销毁资源、无法统一管控的问题,推荐将线程任务定义为Spring管理的原型Bean,配合线程池使用,从根源上解决依赖注入失效的问题。

  1. 修改ThreadExample,添加Spring注解标记为原型Bean:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Scope("prototype") // 每次获取Bean创建新实例,保证线程任务独立
@Component
public class ThreadExample extends Thread {
    @Autowired
    private StudentService studentService; // 注入的是代理后的实例,缓存逻辑正常生效

    @Override
    public void run() {
        studentService.getStudentByID("3", "f");
    }
}
  1. 修改线程启动逻辑,从容器中获取任务实例再启动(生产环境推荐替换为线程池提交任务,降低线程创建销毁的性能开销):
for (int i = 0; i < 24; i++) {
    ThreadExample task = SpringContextUtil.getBean(ThreadExample.class);
    task.start();
}

方案3:补充缓存同步配置,保证同参数并发下方法仅执行一次

完成上述改造后,还需要给@Cacheable注解添加sync = true属性,解决并发场景下的缓存击穿问题,确保同参数请求下方法实际只执行一次:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class StudentService{
    // 开启sync后,同key缓存未命中时会加锁,仅一个线程执行方法,其余线程等待缓存写入
    @Cacheable(value = "student",key = "{#id,#name}", sync = true)
    public Student getStudentByID(String id,String name)
    {
        return new Student(id,name ,"V");
    }
}

注意:sync属性默认仅支持基于ConcurrentHashMap的Spring本地缓存实现,如果使用Redis等分布式缓存,需要额外添加分布式锁来保证多实例部署下的方法单次执行语义。


内容的提问来源于stack exchange,提问作者Tongeless Snake

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 23:27:29