案例15-ArrayList线程不安全,共用全局变量导致数据错乱问题,占用内存情况

简介: 案例15-ArrayList线程不安全,共用全局变量导致数据错乱问题,占用内存情况

背景

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

多个方法一起修改一个公共变量的值,造成数据混乱,导致存入redis中的key值错误

还有每次登陆都会重现创建一个对象,放到公共变量中,遇到并发,对象会被大量地创建,

上一个对象会失去引用,等待垃圾回收器进行回收,导致CPU飙升。

上边公共变量的字符串拼接出现问题,导致下边这张图中的域名中的字符串出现问题。

由上图可知:

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

2、每次给Arraylist重新赋值的时候都创建了一个新的对象,堆积了大量要回收的旧对象,导致CPU飙升**

(GC会消耗大量CPU和内存来实现垃圾回收)

思路&方案

复现问题:

测试类:

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);
    }
}

客户端:

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();
        }
    }
}

正常结果只会出现下面两种情况

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

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

实际上:

注重变量的作用域和生命周期,还要考虑并发量高的时候考虑线程安全,并发的时候还要将对象进行置空。

第一个问题解决方案:

1、在方法之前加 synchronized 关键字。

2、使用ThreadLocal变量。
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());
    }
}

结果:

第二个问题(对象重复创建导致CPU和内存飙升)解决方案:

1、使用List的RemoveAll方法将对象进行清除。

现状:

这样就不会持续开辟内存空间。

总结

考虑成本,凡事都要考虑成本。

我们要有无限思维,当只有一个对象的时候我们写的代码不会出现上述问题,但是对象一多就会出现数据错乱的问题,内存飙升的问题,我们的系统不会只有一个用户,所以无限思维是我们必须要考虑的一件事情,考虑并发,考虑将来。而不是只顾眼前。


相关文章
|
21天前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
54 7
|
1月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
57 11
|
2月前
|
监控 算法 应用服务中间件
“四两拨千斤” —— 1.2MB 数据如何吃掉 10GB 内存
一个特殊请求引发服务器内存用量暴涨进而导致进程 OOM 的惨案。
|
1月前
|
监控 Java 数据库连接
线程池在高并发下如何防止内存泄漏?
线程池在高并发下如何防止内存泄漏?
|
2月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
102 1
|
2月前
|
监控 Java 数据库连接
使用线程池时,如何避免内存泄漏的问题?
使用线程池时,如何避免内存泄漏的问题?
|
2月前
|
监控 数据可视化 Java
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
|
2月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
58 1
C++ 多线程之初识多线程
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
27 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
23 2