谁动了我的内存!

一、背景

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

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

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

  • 引用拷贝:不创建新对象,只是增加了一个指向已存在对象的引用。
  • 浅拷贝:创建一个新对象,但是对象内的引用类型字段还是指向原来的对象。
  • 深拷贝:创建一个完全独立的新对象,包括对象内所有的引用类型字段也各自拷贝了一份。
相关推荐
码农小旋风1 小时前
详解K8S--声明式API
后端
Peter_chq1 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml42 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~2 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616882 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
睡觉谁叫~~~3 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust
2401_865854885 小时前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
AskHarries5 小时前
Spring Boot集成Access DB实现数据导入和解析
java·spring boot·后端
2401_857622665 小时前
SpringBoot健身房管理:敏捷与自动化
spring boot·后端·自动化
程序员阿龙5 小时前
基于SpringBoot的医疗陪护系统设计与实现(源码+定制+开发)
java·spring boot·后端·医疗陪护管理平台·患者护理服务平台·医疗信息管理系统·患者陪护服务平台