一、背景介绍
通过对于项目的部署使用发现,项目在使用不久会就发现非常的卡顿,内容加载不出来,或者加载非常慢的情况。但是每当我们构建完毕之后就不卡了,但是使用一点时间就又会出现卡顿的问题,带着这些现象对服务器进行了监控,看了服务对应的log日志。发现了一下两个问题。
1.从reids中读取数据拼接url地址,存在地址拼接不全的问题
2.遇到并发的情况会产生大量的对象被创建,导致cpu飙升
二、分析问题
两个问题是有前后关系的,当有并发请求到该业务的时候,由于keyList是一个全局变量,别的方法也会对他进行操作,这样就会有第一个方法还没有操作完第二个方法紧接着又进行了操作。最后导致我们拼接出来的key值在redis中是读取不到的。从而出现了拼接url地址确实的问题。对于产生大量的对象,也是由并发造成的,第一个对象刚创建出来,紧接着有创建了第二个对象。又因为对象复制给了一个公共变量,导致上一个对象失去了引用,等待垃圾回收器进行回收。从而cpu直线飙升。
三、如何解决
问题复现
1.操作类
public class Count { List<String> content=new ArrayList<>(); public void getContent(){ content = new ArrayList<>(); content.add("1"); content.add("2"); content.add("3"); content.add("4"); System.out.println("wzill"+content); } }
2.启动类,用100个线程模拟并发的情况
public class Client { public static void main(String[] args) { // 定义线程实现接口 Runnable runnable = new Runnable(){ Count counter = new Count(); @Override public void run() { try { counter.getContent(); } catch (Exception e) { throw new RuntimeException(e); } } }; // 启动100个线程 for( int i= 0;i< 100;i++) { new Thread(runnable).start(); } } }
3.运行结果,出现了数据混乱的情况。
解决问题
1.使用synchronized优化代码
某一时刻只有一个线程在执行,来确保线程数据的安全性。
public class Count { List<String> content=new ArrayList<>(); public synchronized void getContent(){ content = new ArrayList<>(); content.add("1"); content.add("2"); content.add("3"); content.add("4"); System.out.println("wzill"+content); } }
2.使用ThreadLocal
public class Count { private static ThreadLocal<List<String>> keyList = new ThreadLocal<List<String>>(){ //对keyList进行初始化 @Override public List<String> initialValue() { return new ArrayList<String>(); } }; public synchronized void getContent(){ keyList.get().add("1"); keyList.get().add("2"); keyList.get().add("3"); keyList.get().add("4"); System.out.println("wzill"+keyList.get()); } }
3.使用removeAll方法解决创建大量对象问题
//声明一个全局变量,ArrayList线程不安全 List<String> keyList=new ArrayList<>(); public synchronized void count2() throws InterruptedException { keyList.add("a"); keyList.add("b"); keyList.add("c"); keyList.add("d"); System.out.println("ARPRO"+keyList); //由于虽然使用synchronized锁住了count2()这个方法保证同一时刻只有一个线程执行 //但是线程共享全局变量,所以当方法执行完成之后,需要将keyList的值进行还原 keyList.removeAll(keyList); }
四、创建大量无用对象的危害
1.内存占用:每个对象都占用一定的内存空间,如果大量创建无用的对象,会导致内存占用过高,可能引发内存溢出或导致系统性能下降。
2.垃圾回收压力:创建大量无用的对象会增加垃圾回收的负担。垃圾回收器需要扫描和回收无用的对象,如果对象数量过多,会导致垃圾回收的频率增加,降低系统的响应速度。
3.性能下降:创建无用的对象会增加系统的负载,导致性能下降。特别是在循环或递归操作中,如果每次迭代都创建新的对象而不进行复用,会导致性能问题。
五、避免创建大量无用对象
1.对象池:使用对象池技术,提前创建一定数量的对象并保存在池中,需要时从池中获取对象并使用,使用完后放回池中供其他请求使用。这样可以避免频繁创建和销毁对象,提高性能。
2.对象复用:在循环或递归操作中,尽量复用已经创建的对象,避免每次迭代都创建新的对象。可以通过重置对象的状态或属性来复用对象。
3.缓存数据:对于一些频繁使用的数据,可以将其缓存起来,避免重复创建对象。可以使用缓存库或自定义缓存机制来实现数据的缓存。
4.使用对象池的数据结构:在某些情况下,可以使用对象池的数据结构来代替常规的数据结构。例如,使用对象池的字符串构建器代替字符串拼接操作,可以避免创建大量的临时字符串对象。
5.优化算法和代码逻辑:通过优化算法和代码逻辑,减少不必要的对象创建。例如,避免在循环中创建对象,尽量使用基本数据类型代替包装类型等。
六、总结提升
使用全局变量时要慎重,考虑他的作用域以及影响的内容。添加必要的说明,因为作用域大了别人也可以使用这个变量,减少别人使用对之前的业务产生影响。
大量创建无用的对象会导致内存占用、垃圾回收压力和性能下降等问题。为避免这些问题,可以使用对象池、对象复用、缓存数据、使用对象池的数据结构以及优化算法和代码逻辑等方法来减少对象的创建和销毁。