谁动了我的内存!

一、背景

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

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

考虑到缓存,机智的我在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、小结

  • 引用拷贝:不创建新对象,只是增加了一个指向已存在对象的引用。
  • 浅拷贝:创建一个新对象,但是对象内的引用类型字段还是指向原来的对象。
  • 深拷贝:创建一个完全独立的新对象,包括对象内所有的引用类型字段也各自拷贝了一份。
相关推荐
LanLance3 分钟前
ES101系列09 | 运维、监控与性能优化
java·运维·后端·elasticsearch·云原生·性能优化·golang
Piper蛋窝8 分钟前
我所理解的 Go 的 `panic` / `defer` / `recover` 异常处理机制
后端·go
clk66071 小时前
Spring Boot
java·spring boot·后端
皮皮高1 小时前
itvbox绿豆影视tvbox手机版影视APP源码分享搭建教程
android·前端·后端·开源·tv
弱冠少年2 小时前
golang入门
开发语言·后端·golang
Humbunklung2 小时前
Rust 函数
开发语言·后端·rust
喜欢踢足球的老罗2 小时前
在Spring Boot 3.3中使用Druid数据源及其监控功能
java·spring boot·后端·druid
jakeswang2 小时前
StarRocks
后端·架构
龙云飞谷2 小时前
从原理到调参,小白也能读懂的大模型微调算法Lora
后端
荣江2 小时前
【实战】基于 Tauri 和 Rust 实现基于无头浏览器的高可用网页抓取
后端·rust