没弄懂深浅拷贝你也敢用缓存?

简介: 而其中,最简单的肯定就是第一种服务内部的缓存了。强哥前些天就是用了Guava做了缓存,结果因为代码写的有些乱,就出了个匪夷所思的问题。搞了半天最后才发现问题所在。在这里和大家分享一哈。

哈喽,大家好,我是强哥。


缓存对于我们大家来说并不陌生,在我们日常开发中,常用的缓存大概分以下几种类型:


  • 用Java Map或Guava的Cache做服务内部缓存;
  • H2、Derby、HSQLDB等内存数据库做缓存;
  • Redis、Memcache等类型的分布式缓存;


而其中,最简单的肯定就是第一种服务内部的缓存了。强哥前些天就是用了Guava做了缓存,结果因为代码写的有些乱,就出了个匪夷所思的问题。搞了半天最后才发现问题所在。在这里和大家分享一哈。


提出问题


先看工具类代码:


/**
 * Guava缓存工具类
 */
public class GuavaCacheUtils {
    /**
     * 有效时长(秒)
     */
    public static final Integer DURATION_SECOND = 12 * 60 * 60;
    private static Cache<String, Object> localCache = CacheBuilder.newBuilder().
            maximumSize(100). //key大小限制
            expireAfterWrite(DURATION_SECOND, TimeUnit.SECONDS). //缓存保留时长
            build();
    public static void setKey(String key, Object value) {
        localCache.put(key, value);
    }
    public static Object getKey(String key) {
        return localCache.getIfPresent(key);
    }
}


然后就是问题代码:


public void updateUser() {
  //获取用户缓存
  List<User> allUser = (List<User>) GuavaCacheUtils.getKey(ALL_USER_INFO);
  if (CollectionUtils.isEmpty(allUser)) {
      allUser = getAllUser();//数据库获取数据
      GuavaCacheUtils.setKey(ALL_USER_INFO, allUser);
  }
  List<User> users = new ArrayList<>();
  for (User item : allUser) {
      if (item.friends.contain(’小明‘)) {
          users.add(item);
      }
  }
   //加载配置文件
  ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationContext.xml");
  //获取AccountDao实例
  AccountDao accountDao=(AccountDao) applicationContext.getBean("accountDao");
  for (User user : users) {
      //创建Account对象,并向Account对象中添加数据
      Account account=new Account();
      account.setSetAge(user.getAge());
      user.friends = new ArrayList<String>();
      account.setUsername("tom");
      //执行addAccount()方法,并获取返回结果
      int num=accountDao.updateAccount(account);
      if(num>0) {
          System.out.println("成功更新"+num+"条数据!");
      }
  }
}


代码有点长,不过也正是因为写得不清晰,导致了问题的出现。


使用上面的代码之后,在updateUser方法第一次调用时,输出结果为:“成功更新1000条数据!”。可是,在进行第二次方法请求调用时,在没有进行任何外部处理的情况下,却什么都没有输出。而断点调试后发现缓存获取回来的allUser的条数是没有问题的,可是下面这里的判断过后,users的内容却是空的:


List<User> users = new ArrayList<>();
for (User item : allUser) {
    if (item.friends.contain(’小明‘)) {
        users.add(item);
    }
}


这就奇怪了,难不成Guava的缓存有问题,存入缓存里的数据再拿出来,有类型转换或者是什么奇怪的事情发生导致数据没法用了?


可是作为一个广为流传的框架不应该会有这样的问题的,有问题肯定是自己的代码问题。


于是,在经过多次断点调试后,最后终于发现了问题所在。原来是上面的代码改动了缓存的数据,导致再次获取缓存时出了问题。


具体是哪里改了呢?没错,就是这句:


user.friends = new ArrayList<String>();


强哥在发现问题之后,还觉得这个问题有点意思,有种:“我不杀人,别人却因我而死”的味道。


分析问题


既然找到了问题点,那这里也就引入了我们今天要讲的主题深浅拷贝对缓存的影响。


假如我们上面缓存allUser使用的是Redis而不是Guava Cache。那结果肯定是可以正常运行的,为什么呢?


因为Redis是深拷贝,而Guava Cache是浅拷贝。Guava Cache其实内部存储原理类似ConcurrentMap


我们在把数据缓存到Guava Cache中之后,如果之后对存入它的数据引用进行二次操作,其结果是会影响到缓存中的数据的。


也正是因为这个,导致了我们第一次代码运行能正常更新到数据,而第二次却什么也更新不到。


这点确实是在我们使用服务内存缓存时要多注意。浅拷贝类型的内存缓存,在数据存入缓存之后,就最好不再使用该对象或对象引用进行写操作。否则,原本我们的用意是为了使用缓存做快照,存下当时的数据,却会因为后面的修改导致快照被破坏。而且这种问题在代码复杂后就很不容易发现。


当然,解决办法也很简单,就上面的代码而言,我们只要把存入users的数据做一个深拷贝操作,然后再对拷贝后的对象进行操作即可(当然这里只是给个例子,循环里面做深拷贝还是挺影响性能的):


List<User> users = new ArrayList<>();
for (User item : allUser) {
    if (item.friends.contain(’小明‘)) {
        User tmp = SerializationUtils.clone(item);
        users.add(item);
    }
}


Java实现深拷贝


上面我们说了Redis和Guava Cache做缓存在深浅拷贝上的区别。而用一个深拷贝操作解决了问题。那么,Java中有哪些常用的深拷贝方式呢?


强哥也给大家搜到了一个总结,觉得挺好的,具体内容见今天第二篇推文。


16.png

相关文章
|
9月前
|
存储 人工智能 API
Qoder 正式开放订阅,Credits 耐用度提升1/3
Qoder 自 2025 年 8 月 21 日公测以来,以最强的上下文工程能力以及 Repo Wiki、Quest Mode 等广受好评的产品功能,收获了全球开发者的支持和喜爱。今天,Qoder 面向全球用户正式推出付费订阅计划,助力开发者开启高效流畅的编程之旅。
|
5月前
|
关系型数据库 项目管理 数据安全/隐私保护
Leantime:开源项目管理神器
Leantime是一款专为非专业项目经理设计的开源项目管理工具,在Jira的臃肿和Trello的简化之间找到了完美平衡。它集成了战略规划、敏捷看板、甘特图、知识管理、工时跟踪等全面功能,支持Docker一键部署。无论是创业团队还是企业部门,Leantime都能以极低的学习成本,让每位成员轻松参与项目协作。告别过度复杂的工具,用这款轻量而强大的神器,为你的2026年项目计划保驾护航。
789 16
 Leantime:开源项目管理神器
|
9月前
|
SQL 数据可视化 关系型数据库
MCP与PolarDB集成技术分析:降低SQL门槛与简化数据可视化流程的机制解析
阿里云PolarDB与MCP协议融合,打造“自然语言即分析”的新范式。通过云原生数据库与标准化AI接口协同,实现零代码、分钟级从数据到可视化洞察,打破技术壁垒,提升分析效率99%,推动企业数据能力普惠化。
763 3
|
5月前
|
人工智能 测试技术 API
外包项目提效的另一种路径:多模型 AI 的工程价值
外包行业提效困局日益凸显:需求多变、人员流动、周期压缩。单模型AI仅局部优化,难破系统瓶颈。多模型AI以工程化协同替代“人海战术”,通过任务分派、异常降级、流程固化,提升交付稳定性与可扩展性,正成为外包效能升级新路径。
|
10月前
|
JSON 前端开发 关系型数据库
如何物业管理(园区式)系统的房屋及设备设施板块?(附架构图+流程图+代码参考)
本文介绍了园区物业管理系统中房屋与设备设施管理的核心内容,涵盖设备信息、巡检、报修、保养四大功能模块,提供系统架构图、数据模型设计、关键实现建议及可落地的代码样例。通过打通资产与运维流程,实现降本增效、减少停机与投诉,助力运维数据化、智能化。
|
存储 架构师 安全
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
|
8月前
|
数据采集 搜索推荐 API
速来!小红书电商 API 接口,解锁种草数据新玩法
小红书电商API助力开发者高效获取种草数据,涵盖内容推荐、用户行为等核心字段。本文详解API调用、数据清洗与分析技巧,并提供Python实战代码,解锁个性化推荐、市场趋势预测等创新应用,助你快速掌握数据驱动玩法。(238字)
|
9月前
|
人工智能 自然语言处理 API
利用Zyplayer-doc知识库部署企微智能客服
Zyplayer-doc 是一款支持私有化部署的 WIKI 知识库系统,适合个人和企业使用,支持在线文档管理,易上手且成本低。最新版本新增飞书、钉钉、企业微信等平台的 AI 问答接入功能,尤其可与企业微信客服对接,实现智能客服部署。
|
缓存 监控 NoSQL
场景题:线上接口响应慢,应该如何排查问题?
面试中常见的接口响应慢排查题旨在考察研发人员的系统性解决问题的能力。回答时需结合业务场景(如大促、高峰期),并运用工具(Arthas、SkyWalking等)进行监控告警、链路追踪和日志分析,明确问题范围及原因。具体步骤包括:1. 定位问题(确认单个接口或整体系统、查看APM指标、分析链路和日志);2. 排查网络、中间件及外部依赖(检测延迟、检查Redis、RocketMQ、MySQL等);3. 服务端性能分析(CPU、内存、磁盘IO、JVM调优)。最后提出优化方案,如代码逻辑、数据库、缓存策略及资源扩容等。总结时可结合实际案例,展示完整的排查与优化流程。
2725 4
|
11月前
|
机器人 API 数据安全/隐私保护
QQ机器人插件源码,自动回复聊天机器人,python源码分享
消息接收处理:通过Flask搭建HTTP服务接收go-cqhttp推送的QQ消息47 智能回复逻辑

热门文章

最新文章