谁动了我的内存!

一、背景

高流量场景,为了防止数据穿透,减轻数据库压力,如何处理呢?

众所周知,使用缓存使用缓存使用缓存

考虑到缓存,机智的我在redis缓存的基础上再加了一级内存(本地缓存),变成如下结构:

查询逻辑:

先查询内存数据 ------> 无数据则再查询redis ------> 无数据则再查询兜底数据。

还加了兜底数据,简直绝绝子。

压测一波,单副本qps2500,简直完美。

二、问题

接口逻辑:第一条数据是kyc匹配(每个用户返回可能不一样),其他是剩余的列表中的内容,第一条数据会打上tag标签。

信心满满上线,结果竟然出问题了!!!返回的结果中竟然出现了多条数据都有tag标签,为什么?

代码如下:

ini 复制代码
// 从内存中获取数据
List<ScIndexAnalysisDTO> cacheList = analysisService.getScIndexAnaList();
 
List<ScIndexAnalysisDTO> analysisDTOList = new ArrayList<>(cacheList);
 
// 首条数据打上tag
analysisDTOList.get(0).setTag(analysisConfig.getTag());
 
return ScIndexAnalysisConverter.INSTANCE.toVOList(analysisDTOList);

看起来相当正常,甚至还new了一个list来接收,为什么会出问题呢?

原理:

new ArrayList<>(cacheList)这中方式采用的是引用拷贝, Java中的对象都是按引用传递,所以上述代码中的 cacheList 和 analysisDTOList 其实指向的是同一个对象。

当设置tag时,直接修改了原对象中的tag,由于不同用户请求时接口返回值中第一条数据会不一样,导致打上tag标签的数据不一样,最终造成返回结果中有多个tag标签。

三、解决方案

1、不修改原对象

将首条数据打上tag标签这一步放在数据转换之后

ini 复制代码
// 从内存中获取数据
List<ScIndexAnalysisDTO> cacheList = analysisService.getScIndexAnaList();
 
List<ScIndexAnalysisDTO> analysisDTOList = new ArrayList<>(cacheList);
 
 List<ScIndexAnalysisVO> finalAnaDtoList= ScIndexAnalysisConverter.INSTANCE.toVOList( analysisDTOList );
// 首条数据打上tag
finalAnaDtoList.get(0).setTag(analysisConfig.getTag());
 
return finalAnaDtoList;

converter转换的过程其实也是深拷贝。

2、一定要改变属性值,如何处理呢

采用深拷贝来实现!

有两种方法:

(1)ScIndexAnalysisDTO对象implement Cloneable 并实现一个clone方法

java 复制代码
public class ScIndexAnalysisDTOimplements Cloneable{
    private String name;
     
    //实现这个方法
    protected ScIndexAnalysisDTOimplements clone() throws CloneNotSupportedException {
        return (ScIndexAnalysisDTOimplements)super.clone();
    }
}

(2) 手动赋值

ini 复制代码
List<ScIndexAnalysisDTO> b = new ArrayList<>();
for (ScIndexAnalysisDTO element: a) {
   ScIndexAnalysisDTO dto = new ScIndexAnalysisDTO();
   dto.setId(element.getId());
    dto.setName(element.getName());
   b.add(dto);
}

四、总结

在Java中,对象的拷贝可以分为引用拷贝、浅拷贝和深拷贝三种方式,它们各自有不同的特点和应用场景。

**

1、引用拷贝(Reference Copy)

引用拷贝是最简单的一种拷贝方式,它并不创建一个新的对象,只是将新的引用指向已经存在的对象。因此,通过任何一个引用所做的改动都会反映到这个对象上。

示例 复制代码
Person original = new Person("Tom", 30);
Person copy = original;

copy和original都指向了同一个Person对象。如果我们通过copy修改了对象的属性,那么通过original也能看到这个改变。

2、浅拷贝(Shallow Copy)

浅拷贝创建了一个新的对象,但是对于对象内部的非基本类型的字段仍然采用引用拷贝。这意味着,如果原对象中的字段是引用类型,那么在浅拷贝后,原对象和拷贝对象会共享这些引用类型的字段。

示例 复制代码
class Person implements Cloneable {
    String name;
    int age;
    Address address; // 假设Address是另一个包含街道信息的类
 
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
 
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
 
// 使用浅拷贝
Person original = new Person("Tom", 30, new Address("Some Street"));
Person shallowCopy = (Person) original.clone();

在这个例子中,shallowCopy是original的浅拷贝。shallowCopy和original将共享相同的Address对象。

3、深拷贝(Deep Copy)

深拷贝不仅复制了对象本身,还复制了对象内部所有引用类型的成员。这意味着原对象和拷贝对象在内存中是完全独立的,对一个对象的修改不会影响到另一个。

示例 复制代码
class Person implements Cloneable {
    String name;
    int age;
    Address address;
 
    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }
 
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.address = (Address) this.address.clone(); // 深拷贝
        return cloned;
    }
}
 
// 使用深拷贝
Person original = new Person("Tom", 30, new Address("Some Street"));
Person deepCopy = (Person) original.clone();

在进行深拷贝时,需要确保所有引用类型的字段也支持克隆(例如,这里的Address类也需要实现Cloneable接口并重写clone方法)。

4、小结

  • 引用拷贝:不创建新对象,只是增加了一个指向已存在对象的引用。
  • 浅拷贝:创建一个新对象,但是对象内的引用类型字段还是指向原来的对象。
  • 深拷贝:创建一个完全独立的新对象,包括对象内所有的引用类型字段也各自拷贝了一份。
相关推荐
wn53131 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
希冀1231 小时前
【操作系统】1.2操作系统的发展与分类
后端
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
爱上语文2 小时前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
serve the people2 小时前
springboot 单独新建一个文件实时写数据,当文件大于100M时按照日期时间做文件名进行归档
java·spring boot·后端
罗政8 小时前
[附源码]超简洁个人博客网站搭建+SpringBoot+Vue前后端分离
vue.js·spring boot·后端
拾光师9 小时前
spring获取当前request
java·后端·spring
Java小白笔记11 小时前
关于使用Mybatis-Plus 自动填充功能失效问题
spring boot·后端·mybatis
JOJO___13 小时前
Spring IoC 配置类 总结
java·后端·spring·java-ee
白总Server13 小时前
MySQL在大数据场景应用
大数据·开发语言·数据库·后端·mysql·golang·php