使用 synchronized 解决多线程并发问题 – Java 实战案例 | 幽络源

使用 synchronized 解决多线程并发问题 – Java 实战案例 | 幽络源

在 Java 项目中,我们经常会遇到多线程并发问题,尤其是在生成唯一标识符的场景中。本文将结合实际项目案例,详细讲解如何使用 synchronized 解决多线程并发导致的任务编号重复问题。

问题背景

在我们的项目中,有一个创建任务的功能。每次创建任务时,系统会根据当前时间(年月日时分秒)生成一个任务编号,代码如下:

String taskNum=LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));

理论上,每次生成的任务编号应该是唯一的。然而,在高并发场景下,多个线程同时调用该接口时,可能会生成相同的任务编号,这显然是不合理的。如图

1

问题复现

为了验证问题,我添加了输出开始时间和结束时间,并且精确到毫秒,因为这个接口响应时间在100~300毫秒,如果精确到秒,出现在同一时间那是必然的,看如果是毫秒级别,能否就直接解决这个问题了,代码类似如下

/**
    * 创建任务-提交
    *
    * @param dto 创建任务提交的数据对象
    * @return
    */
   @Override
   @Transactional
   public int addTask(TaskCreateDTO dto,String userId) {
       System.out.println("开始"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
​
       String taskNum=LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
       System.out.println("任务编号:"+taskNum);
       //一些业务逻辑........
       System.out.println("结束"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
​
       return 1;
  }

启动项目,并用APIfox进行并发测试,如图可以看到,即使精确到毫秒级,还是会生成出相同的编号,这说明在高并发场景下,多个线程同时进入了该方法。

2

问题分析

问题的根本原因是多个线程在同一时间调用了该方法,导致生成的任务编号相同。为了解决这个问题,我们需要确保同一时间只有一个线程可以执行该方法。

解决方案

在 Java 中,synchronized 关键字可以确保同一时间只有一个线程执行被修饰的方法。但需要注意的是,synchronized 的有效性依赖于方法所在类的实例作用域。

  • 单例模式:如果类的作用域是单例(Spring 默认),则可以直接使用 synchronized 修饰方法。

  • 多实例模式:如果类的作用域是多实例,则需要额外处理(例如使用类锁或静态锁)。

在我们的项目中,通过输出 this.hashCode() 验证了当前类是单例的:

System.out.println("当前实例哈希码: " + this.hashCode());

3

如上图,可以看到,每此在方法中输出的this的哈希码都是一样的,表示当前的实现类对象在整个程序中是单实例的,因此直接在方法上加上synchronized即可解决此问题

验证结果

代码如下

@Override
   @Transactional
   public synchronized int addTask(TaskCreateDTO dto,String userId) {
       System.out.println("开始"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
​
       String taskNum=LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
       System.out.println("任务编号:"+taskNum);
       //一些业务逻辑........
       System.out.println("结束"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
​
       return 1;
  }

再次使用 APIfox 进行并发测试,结果如下:

  • 每次生成的任务编号都是唯一的。

  • 多个线程依次执行该方法,避免了并发问题。

如图

4

注意事项

虽然 synchronized 可以有效解决并发问题,但在高并发场景下,可能会带来性能瓶颈。因此,建议在以下情况下使用:

  1. 并发量较低:适合并发量较小的场景。

  2. 方法执行时间较短:适合执行时间较短的方法。

  3. 需要严格保证线程安全:适合对数据一致性要求较高的场景。

如果并发量较大或方法执行时间较长,可以考虑其他优化方案,例如减小锁粒度、使用读写锁或无锁编程。

结语

通过幽络源本文的案例,我们深入了解了如何使用 synchronized 解决多线程并发问题。如果你对 Java 多线程编程感兴趣,欢迎加入我们的学习交流群(Q群:307531422),获取更多源码、技术和网创教程。

© 版权声明
THE END
喜欢就支持一下吧
分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称快捷回复

    暂无评论内容