Java 并发问题
问题引述:最近工作写代码涉及线程的时候,出现了一个 bug,所以写这篇文章记录一下这个问题.
1.知识准备
Java 虚拟机(JVM)是由堆内存、虚拟机栈、本地方法栈、方法区 、程序计数器、五部分组成
堆内存:存放实例化对象,Java 中 new 的对象就存放在堆内存中
虚拟机栈;是线程私有的,生命周期与线程相同,存放局部变量,变量名等信息
本地方法栈:是用来供 Java 调用本地服务(Native)用的
方法区:是用来存放静态变量,缓存代码的地方
程序计数器:可以看作当前线程所执行的命令指示器.
2.问题描述
案例:
这段代码是直接在 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 如下:
后记:bug 相对简单,但是找资料还是有点困难 如果觉得对你有帮助就点赞支持一下!
文章转载自:土豆663
评论