目录
背景
存入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方法将对象进行清除。
现状:
这样就不会持续开辟内存空间。
总结
考虑成本,凡事都要考虑成本。
我们要有无限思维,当只有一个对象的时候我们写的代码不会出现上述问题,但是对象一多就会出现数据错乱的问题,内存飙升的问题,我们的系统不会只有一个用户,所以无限思维是我们必须要考虑的一件事情,考虑并发,考虑将来。而不是只顾眼前。
并发如何解决?
并发问题是在多线程或多进程环境中经常出现的挑战,可能导致数据不一致、死锁等问题。解决并发问题需要考虑各种技术和策略,下面是一些常见的方法:
锁机制: 使用锁是解决并发问题的一种常见方法。通过在关键代码段周围加锁,只允许一个线程或进程访问临界资源,从而防止多个线程同时修改相同数据。然而,锁可能导致死锁和性能问题,因此需要小心设计和管理。
同步机制: 同步方法如synchronized块或方法可以确保在同一时间只有一个线程可以访问共享资源。这有助于避免数据竞争和不一致问题。
并发容器: Java提供了许多并发容器,如ConcurrentHashMap、ConcurrentLinkedQueue等,这些容器在多线程环境中提供了更好的性能和线程安全性。
原子操作: 原子操作是不可分割的操作,可以确保多线程环境中的数据一致性。Java提供了一些原子类,如AtomicInteger、AtomicReference等,用于执行原子操作。
线程池: 使用线程池可以有效地管理并发任务。线程池可以控制线程的数量,从而减少线程创建和销毁的开销,提高性能和资源利用率。
避免共享状态: 尽量避免共享状态,通过将状态封装在对象中,每个线程操作自己的对象实例,从而避免竞争和并发问题。
使用不可变对象: 不可变对象在多线程环境中是线程安全的,因为它们不会改变。使用不可变对象可以避免并发问题。
内存模型和可见性: 了解Java内存模型和可见性原则,确保一个线程对共享变量的修改对其他线程可见。
死锁避免和解决: 死锁是一种并发问题,当多个线程互相等待对方释放资源时发生。避免死锁可以通过破坏四个必要条件之一来实现:互斥、持有并等待、不可抢占、循环等待。
并发工具: Java提供了许多并发工具,如CountDownLatch、CyclicBarrier、Semaphore等,用于协调多个线程的执行和等待。
总之,解决并发问题需要综合考虑设计、同步、数据管理和线程控制等方面。选择适当的策略和技术取决于问题的复杂性和性能要求。同时,良好的并发编程实践也需要深入了解多线程和并发编程的原则。
版权归原作者 Circ. 所有, 如有侵权,请联系我们删除。