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

2023-01-03 0 1,080

哈喽,他们好,我是林克。

缓存对他们他们而言并不孤单,在他们日常生活合作开发中,常见的缓存约莫分下列三种类别:

用Java Map或Guava的Cache做服务项目外部缓存;H2、Derby、HSQLDB等缓存数据库做缓存;Redis、Memcache等类别的分布式系统缓存;

而当中,最简单的的确是第二种服务项目外部的缓存了。林克博纳瓦县是用了Guava做了缓存,结论即使标识符写的很多乱,就出了个不可思议的难题。搞了半天最终才辨认出Vizille。在这儿和他们撷取一哈。

明确提出难题

先看辅助工具类标识符:

/** * 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, Objectvalue) { localCache.put(key, value); }public static Object getKey(String key) { returnlocalCache.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 = newArrayList<>();for (User item : allUser) { if (item.friends.contain(’小明‘)) { users.add(item); } } //加载配置文件 ApplicationContext applicationContext=new ClassPathXmlApplicationContext(“applicationContext.xml”); AccountDao accountDao=(AccountDao) applicationContext.getBean(“accountDao”); for(User user : users) {//创建Account对象,并向Account对象中添加数据 Account account=newAccount(); account.setSetAge(user.getAge()); user.friends =new ArrayList<String>(); account.setUsername(“tom”); //执行addAccoun 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 = newArrayList<>();for (User item : allUser) { if (item.friends.contain(’小明‘)) { User tmp = SerializationUtils.clone(item); users.add(item); } }

Java实现深复本

上面他们说了Redis和Guava Cache做缓存在厚薄复本上的区别。而用一个深复本操作解决了难题。那么,Java中有哪些常见的深复本方式呢?

林克也给他们搜到了一个总结,觉得挺好的,具体内容见今天第二篇推文。

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

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务