网站首页 > java教程 正文
哈喽,大家好,我是强哥。
缓存对于我们大家来说并不陌生,在我们日常开发中,常用的缓存大概分以下几种类型:
- 用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中有哪些常用的深拷贝方式呢?
强哥也给大家搜到了一个总结,觉得挺好的,具体内容见今天第二篇推文。
猜你喜欢
- 2024-10-19 Python 中赋值、浅拷贝、深拷贝的区别是什么?
- 2024-10-19 Java 对象拷贝原理剖析(java 对象拷贝原理剖析)
- 2024-10-19 认识Object类和深浅拷贝!(阐述object.assign的用法,深拷贝与浅拷贝的区别?)
- 2024-10-19 三面“有赞”Java岗斩获offer:Spring+JVM+并发锁+分布式+算法
- 2024-10-19 谈谈 Java 开发中的对象拷贝(java对象拷贝工具类)
- 2024-10-19 深入浅出Java中的clone克隆方法,写得也太棒了
- 2024-10-19 深拷贝和浅拷贝之list、dataframe
- 2024-10-19 对象拷贝java 浅谈(java对象的拷贝)
- 2024-10-19 Java克隆对象需要知道的事(java克隆的作用)
- 2024-10-19 Java的Object十二个常用方法,你用过几个?
你 发表评论:
欢迎- 最近发表
-
- 多种负载均衡算法及其Java代码实现
- 输入www.baidu.com背后经历了啥?说清楚这个,已经超过90%的人了
- 优化MySQL:为什么你应该用 UNSIGNED INT 存储IP地址
- 实模式下CPU如何获取数据及指令(实模式寻址方式)
- java基础都在这了,小主们拿去吧(java基础是指什么)
- 盘点爬虫语言为何选择Python而不是Java
- 搭载Dubbo+Zookeeper踩了这么多坑,我终于决定写下这篇
- 网络协议之TCP/IP协议(面试必考内容) - javaEE初阶 - 细节狂魔
- 深夜报警!10亿次请求暴击,如何用Redis找出最热IP?
- VPN技术(IPsec/L2TP/SSLVPN/PPTP)学习笔记
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)