Java多线程对于成员变量和局部变量的影响

简介: Java多线程对于成员变量和局部变量的影响

今天配合同事一起和外部系统进行联调测试,其实昨天我们已经成功走通了一遍。今天同事得到对方反馈系统可能有一个潜在的问题,所以就又严格地联调了一遍。这一遍,我也是一遍又一遍地盯日志,关注告警邮件。正是在这一遍联调系统中,我发现了一个小问题,程序里面一封相同内容的通知邮件,几乎是同一个时间发送了两次。


通过分析日志外加比对代码,终于找到问题,是遇到线程安全问题引起的。下面和大家分享一下,伪代码:

public class Email{
  private String emailContent;
  @Async
  public void sendEmail(){
      "send email" + emailContent;
  }
}
//Email类的实例被Spring容器管理,只有一个实例emailInstance。
public class TestDemo{
  @Resource
  Email emailInstance;
  @Async
  public void testMethod(){
      lock();
        //处理业务逻辑,同时操作emailInstance.emailContent;
        operationBs();
        emailInstance. sendEmail();
      unlock();
  }
}


问题原因复述:testMethod方法为防止多线程同时操作,在此处使用了锁,而为了确保发送邮件不影响主程序的执行时间,所以在调用sendEmail方法时,另行开辟了异步线程处理。正是这个方法允许了异步线程处理,所以当第一个线程释放锁后,而异步线程尚未完成邮件发送时,第二个获得锁并执行sendEmail方法时,开启了新的异步线程处理邮件发送,这样就出现了多个线程共同使用emailInstance对象,并同时操作emailInstance的成员变量emailContent。


因为如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作时,他们对该成员变量的操作是彼此影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。这样就会出现上述发送相同内容邮件的情况。


理论讲解:JAVA 多线程同时调用单例模式的对象时,该对象中的对成员变量与局部变量是否会受到多个线程的影响?


当多个线程对同一个单例对象的同一个成员变量进行操作时,它们对该成员变量的操作是彼此互相影响的(也就是说一个线程对该成员变量的改变会影响到另一个线程) 。对于成员变量的操作,可以使用ThreadLocal来保证线程安全。


而多线程调用同一个对象的同一个方法时,每个线程会对方法内部的局部变量都是在线程自己独立的内存区域进行的,也就是说在每个线程的独立内存中都一个局部变量的拷贝,这样一个线程对同一个单例对象的同一方法内的局部变量的改变就不会影响到其他线程中的局部变量,所以是线程安全的。


总结,局部变量不会受多线程影响,成员变量会受到多线程影响。多个线程调用同一个对象的同一个方法时,如果方法里无成员变量,那么不受任何影响;如果方法里有成员变量,只有读操作,不受影响,存在写操作,考虑多线程影响值。


解决方案:


  1. 简化方案,去掉Email中sendEmail方法上@Async注解,也就是说将处理业务逻辑和发送邮件合并为同步操作,这样就保证了同一时间只会有一个线程操作成员变量,这样也就避免了线程安全问题。但注意这是以牺牲核心业务逻辑的处理时间来换得安全。
  2. 将操作成员变量,调整为操作局部变量。


参考文档:


Java 多线程(四) 多线程访问成员变量与局部变量

相关文章
|
5天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
1天前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
8 1
|
5天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
12 3
|
5天前
|
缓存 安全 Java
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文将深入探讨Java中的多线程编程,包括其基本原理、实现方式以及常见问题。我们将从简单的线程创建开始,逐步深入了解线程的生命周期、同步机制、并发工具类等高级主题。通过实际案例和代码示例,帮助读者掌握多线程编程的核心概念和技术,提高程序的性能和可靠性。
8 2
|
6天前
|
Java
Java中的多线程编程:从基础到实践
本文深入探讨Java多线程编程,首先介绍多线程的基本概念和重要性,接着详细讲解如何在Java中创建和管理线程,最后通过实例演示多线程的实际应用。文章旨在帮助读者理解多线程的核心原理,掌握基本的多线程操作,并能够在实际项目中灵活运用多线程技术。
|
6天前
|
Java 开发者
Java中的多线程基础与应用
【10月更文挑战第24天】在Java的世界中,多线程是提高效率和实现并发处理的关键。本文将深入浅出地介绍如何在Java中创建和管理多线程,以及如何通过同步机制确保数据的安全性。我们将一起探索线程生命周期的奥秘,并通过实例学习如何优化多线程的性能。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开一扇通往高效编程的大门。
11 0
|
26天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
38 1
C++ 多线程之初识多线程
|
10天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
12 3
|
10天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
10 2
|
10天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
22 2