找回密码
 立即注册
首页 业界区 业界 Java并发问题

Java并发问题

卓卞恻 2025-9-26 11:01:12
问题引述:最近工作写代码涉及线程的时候,出现了一个bug,所以写这篇文章记录一下这个问题.
1.知识准备

Java虚拟机(JVM)是由堆内存、虚拟机栈、本地方法栈、方法区 、程序计数器、五部分组成

  • 堆内存:存放实例化对象,Java中new的对象就存放在堆内存中
  • 虚拟机栈;是线程私有的,生命周期与线程相同,存放局部变量,变量名等信息
  • 本地方法栈:是用来供Java调用本地服务(Native)用的
  • 方法区:是用来存放静态变量,缓存代码的地方
  • 程序计数器:可以看作当前线程所执行的命令指示器.
2.问题描述

案例:
  1. // 1. 从数据库查询当前任务状态
  2. Task task = taskService.selectOne(wrapper);
  3. // 2. 在内存中计算新的执行次数:当前次数 + 1
  4. int num = task.getNum() + 1;
  5. // 3. 设置新次数到内存对象
  6. task.setNum(num);
  7. // 4. 更新数据库记录
  8. taskMapper.updateById(task);
复制代码

  • 这段代码是直接在Java虚拟机中运行的,当同一时间多个线程执行这段代码时,任务的num就会混乱.原因在于每个线程操作的都是自己工作内存中的task对象副本(原始对象在堆中,但读取和修改都是发生在线程的上下文中),即一个线程对堆内存对象的修改对另一个线程不是立即可见的,且数据库的更新存在延迟.
举个例子: 假设任务Task还未执行过   线程A这时执行一次任务,首先它从堆内存读取一次num,   num=num+1 得到   num=1  在它要更新数据库的时候 此时线程B 也来执行该任务, 由于线程A的num = 1 还未及时更新到数据库  导致 B从数据库中读出来的 num = 0.这时,线程A更新完 => num=1,线程B更新完 => num=1 导致次数记录错误.
3.解决方法:


  • 找资料后发现,我们使用的数据库是 MYSQL(默认存储引擎InnoDB) 提供了行级锁机制.InnoDB行级锁又分为两种 共享锁(Read Lock)与排他锁(Write Lock), 这里就不展开谈了,顾名思义 当执行UPDATE . DELETE 语句时InnoDB默认会自动给WHERE条件匹配的行添加排他锁(Write Lock),一个事务持有某行的Write Lock时,其他事务无法再对该行操作.
  • 所以我们可以把这个并发问题交给数据库,即当线程A 去更新记录时,线程B也要去更新 这时候由于Write Lock的存在导致线程B需要等线程A执行完后才能去更新,这样就可以解决问题.具体的sql如下:
  1. //原理 使用这条sql语句 在数据库执行的是原子操作,数据库保证 1.读值,2.计算,3.更新这三个操作不可分隔
  2. update task set num = num + 1 where task_id = #{taskId}
复制代码
后记:bug相对简单,但是找资料还是有点困难 如果觉得对你有帮助就点赞支持一下!

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册