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 多线程(四) 多线程访问成员变量与局部变量

相关文章
|
3天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
5天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
5天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
6天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
22 3
|
6天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
66 2
|
4月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
139 1
|
7月前
|
设计模式 监控 Java
Java多线程基础-11:工厂模式及代码案例之线程池(一)
本文介绍了Java并发框架中的线程池工具,特别是`java.util.concurrent`包中的`Executors`和`ThreadPoolExecutor`类。线程池通过预先创建并管理一组线程,可以提高多线程任务的效率和响应速度,减少线程创建和销毁的开销。
242 2
|
7月前
|
Java 数据库
【Java多线程】对线程池的理解并模拟实现线程池
【Java多线程】对线程池的理解并模拟实现线程池
75 1
|
4月前
|
安全 算法 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(下)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
87 6
|
4月前
|
存储 安全 Java
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)(中)
17 Java多线程(线程创建+线程状态+线程安全+死锁+线程池+Lock接口+线程安全集合)
94 5