28个案例问题分析---15---登陆之后我加入的课程调用接口报错--ArrayList线程不安全。占用内存情况

简介: 28个案例问题分析---15---登陆之后我加入的课程调用接口报错--ArrayList线程不安全。占用内存情况

ArrayList线程不安全。占用内存情况

4.2 重复创建对象造成内存空间浪费

一:故事背景

1.1 问题描述

存入redis的值,可能会出现错误的情况。如果出现错误,接口将会报错

1.2 问题原因

排查之发现是。对ArrayList的使用问题。问题主要有两个:

1.使用了线程不安全的ArrayList作为公共变量

2.每次给ArrayList重新赋值的时候都创建了一个新的变量,导致内存飙升问题

二:问题复现

2.1 ThreadTest 代码

我们用一个例子来复现一下这个问题

测试的类

public class ThreadTest {
    //新建一个list作为成员变量
    List<String> testList ;
    public void updateTestList(){
        testList = new ArrayList<>();
        testList.add("a01+");
        testList.add("a02+");
        testList.add("a03+");
        testList.add("a04+");
        //打印一下看看有什么
        System.out.println("updateTestList"+testList);
    }
    public void updateTestList2(){
        testList = new ArrayList<>();
        testList.add("b01+");
        testList.add("b02+");
        testList.add("b03+");
        testList.add("b04+");
        //看一下list里有什么
        System.out.println("updateTestList2"+testList);
    }
}

上述代码创建了一个List作为成员变量,在下面的方法中,重新new了一个List对象,并且让成员变量的指针指向新创建的List对象。

2.2 main函数 代码

测试代码,通过多线程的方式,模拟并发执行

public class Main {
    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        //开一个多线程测试一下
        for (int i = 0; i < 100; i++) {
          Thread thread = new Thread(new Runnable() {
              @Override
              public void run() {
                  threadTest.updateTestList();
                  threadTest.updateTestList2();
              }
          });
        thread.start();
        }
    }
}

2.3 执行结果

2.4 结果分析

结果中可能会报错 java.util.ConcurrentModificationException 这个错是由于使用list的时候对list进行修改导致的

在结果里我们可以看到,我们声明的 testList 在一个线程操作的时候,另外的线程对它进行了修改。

如果他是线程安全的话打印的值只有两种可能

updateTestList[a01+, a02+, a03+, a04+]

updateTestList2[b01+, b02+, b03+, b04+]

所以我们得出结论:

ArrayList 是线程不安全的

三: 问题解决

上文提到我们一共有两个问题,这里先解决线程不安全的问题

3.1 在这两个方法之前添加 synchronized 关键字。

如何解决我们上述的这个问题呢?简单的方法就是给我们定义的updateTestList进行上锁。

看一下结果:


我们可以发现,添加上synchronized 关键字之后,执行没有问题了。通过加锁,保证线程安全。


3.2 使用ThreadLocal变量。

这个我之前的博客有写过,大家可以观看。

在线人员逻辑反例–ThreadLocal、继承、索引失效、

使用ThreadLocal声明变量,他会为每个线程都创建一个变量。注意内存消耗


3.2.1 使用方法

public class ThreadTest2 {
    ThreadLocal<List<String>> testList = ThreadLocal.withInitial(()->new ArrayList<>());
    public  void updateTestList(){
        testList.get().removeAll(testList.get());
        testList.get().add("a01+");
        testList.get().add("a02+");
        testList.get().add("a03+");
        testList.get().add("a04+");
        //打印一下看看有什么
        System.out.println("updateTestList"+testList.get());
    }
    public  void updateTestList2(){
        testList.get().removeAll(testList.get());
        testList.get().add("b01+");
        testList.get().add("b02+");
        testList.get().add("b03+");
        testList.get().add("b04+");
        //看一下list里有什么
        System.out.println("updateTestList2"+testList.get());
    }
}

3.2.2 对应结果

3.3 解决重复创建对象问题。

这里解决第问题,重复创建对象造成的内存空间浪费问题:

3.3.1 问题复现

我们重点来看一下这部分代码每次调用updateTestList方法的时候,都会重新创建一个新的对象。然后之前的对象由于失去引用,等待

GC回收。并发上来之后,就会导致内存上升。

3.3.2 内存变化

内存变化图:

每调用一次方法,都会去创建新的对对象,并且将testList的引用指向新的地址。

3.3.1 解决方法

解决方法很简单,我们业务的需要时将这个List清空,我们只需要调用 removeALL方法就够了,这样的话,在堆里始终是同一块内存地址。不会持续开辟内存空间


四:总结&升华

4.1 ArrayList线程不安全

Java中的ArrayList是线程不安全的,因为它不是同步的(unsynchronized)。具体来说,当多个线程同时对同一个ArrayList实例进行修改操作时,可能会出现不可预期的结果。


例如,当一个线程在执行add()操作添加元素时,另一个线程可能正在执行remove()操作删除元素,这样就会破坏ArrayList内部的数据结构。另外,由于ArrayList是动态数组,在增加或删除元素时需要重新分配内存空间,这也会增加线程不安全的风险。


为了避免这种情况,Java提供了同步的(synchronized)版本Vector,或者使用并发安全的CopyOnWriteArrayList。在多线程环境中,建议使用这些线程安全的集合类。我们这里没有讲解以后有机会将会更新。

4.2 重复创建对象造成内存空间浪费

Java中的ArrayList是线程不安全的,因为它不是同步的(unsynchronized)。具体来说,当多个线程同时对同一个ArrayList实例进行修改操作时,可能会出现不可预期的结果。


例如,当一个线程在执行add()操作添加元素时,另一个线程可能正在执行remove()操作删除元素,这样就会破坏ArrayList内部的数据结构。另外,由于ArrayList是动态数组,在增加或删除元素时需要重新分配内存空间,这也会增加线程不安全的风险。


为了避免这种情况,Java提供了同步的(synchronized)版本Vector,或者使用并发安全的CopyOnWriteArrayList。在多线程环境中,建议使用这些线程安全的集合类。


虽然,我们这里创建List可能开销不是很大,但是这是完全无意义的开销。只有看到这种小点,培养我们的编码习惯和边界,才能避免出更大的错误。


目录
相关文章
|
2月前
|
SQL 存储 Java
关于内存安全问题,你应该了解的几点!
关于内存安全问题,你应该了解的几点!
|
3月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
71 11
|
3月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
105 11
|
3月前
|
NoSQL 程序员 Linux
轻踩一下就崩溃吗——踩内存案例分析
踩内存问题分析成本较高,尤其是低概率问题困难更大。本文详细分析并还原了两个由于动态库全局符号介入机制(it's a feature, not a bug)触发的踩内存案例。
|
4月前
|
数据采集 Rust 安全
Rust在网络爬虫中的应用与实践:探索内存安全与并发处理的奥秘
【8月更文挑战第31天】网络爬虫是自动化程序,用于从互联网抓取数据。随着互联网的发展,构建高效、安全的爬虫成为热点。Rust语言凭借内存安全和高性能特点,在此领域展现出巨大潜力。本文探讨Rust如何通过所有权、借用及生命周期机制保障内存安全;利用`async/await`模型和`tokio`运行时处理并发请求;借助WebAssembly技术处理动态内容;并使用`reqwest`和`js-sys`库解析CSS和JavaScript,确保代码的安全性和可维护性。未来,Rust将在网络爬虫领域扮演更重要角色。
80 1
|
4月前
【BUG记录】力扣报错:内存空间不足
【BUG记录】力扣报错:内存空间不足
|
4月前
|
Rust 安全 程序员
揭秘Rust语言的内存安全秘籍:如何构建坚不可摧的系统级应用?
【8月更文挑战第31天】Rust语言凭借其独特内存安全机制在编程领域脱颖而出,通过所有权、借用与生命周期等概念,在保证高性能的同时避免了缓冲区溢出等常见错误。本文深入探讨Rust的内存安全机制,并通过示例代码展示如何利用这些机制构建高效且可靠的系统。尽管这些机制增加了学习难度,但为软件开发奠定了坚实基础,使Rust成为系统、嵌入式及网络编程的理想选择。随着社区的发展,Rust将在未来软件开发中扮演更重要角色。
89 0
|
5月前
|
移动开发 监控 Serverless
函数计算操作报错合集之机器配置显示为1G内存,但报错显示0.12G,是什么原因
在使用函数计算服务(如阿里云函数计算)时,用户可能会遇到多种错误场景。以下是一些常见的操作报错及其可能的原因和解决方法,包括但不限于:1. 函数部署失败、2. 函数执行超时、3. 资源不足错误、4. 权限与访问错误、5. 依赖问题、6. 网络配置错误、7. 触发器配置错误、8. 日志与监控问题。
|
5月前
|
移动开发 运维 JavaScript
阿里云云效操作报错合集之遇到Node.js的内存溢出问题,该怎么办
本合集将整理呈现用户在使用过程中遇到的报错及其对应的解决办法,包括但不限于账户权限设置错误、项目配置不正确、代码提交冲突、构建任务执行失败、测试环境异常、需求流转阻塞等问题。阿里云云效是一站式企业级研发协同和DevOps平台,为企业提供从需求规划、开发、测试、发布到运维、运营的全流程端到端服务和工具支撑,致力于提升企业的研发效能和创新能力。
|
5月前
|
DataWorks 关系型数据库 API
DataWorks操作报错合集之使用PolarDB Reader时,遇到报错:内存溢出,该如何解决
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。