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可能开销不是很大,但是这是完全无意义的开销。只有看到这种小点,培养我们的编码习惯和边界,才能避免出更大的错误。


目录
相关文章
|
3月前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
4月前
|
编译器 C语言
动态内存分配与管理详解(附加笔试题分析)(上)
动态内存分配与管理详解(附加笔试题分析)
97 1
|
5月前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
73 2
|
2月前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
232 7
|
3月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
99 1
|
3月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
75 3
|
3月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
3月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
508 9
|
4月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
4月前
|
程序员 编译器 C语言
动态内存分配与管理详解(附加笔试题分析)(下)
动态内存分配与管理详解(附加笔试题分析)(下)
68 2