You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

并发环境下读取值时为何需要读锁?无锁读取自定义Counter类会引发哪些问题?

并发读取与读锁问题解答

1. 为什么并发环境中读取值时需要读锁?

在并发场景下,读锁的核心作用是解决三个关键问题:

  • 可见性保证:每个线程都有自己的工作内存,会缓存共享变量的值。如果没有读锁这类同步机制,读线程可能一直从本地工作内存读取旧值,永远看不到其他线程写入主内存的最新数据。
  • 避免拆分读写的中间态:对于Java中的long和double这类64位类型,JVM允许把单次写操作拆分成两个32位的写操作执行。如果没有读锁和写锁的互斥,读线程可能恰好读到写线程只完成一半的数值——得到一个既不是旧值也不是新值的错误“垃圾值”。
  • 读写操作的逻辑一致性:读锁能和写锁互斥,保证在读操作的整个过程中,不会有写线程修改目标数据。举个例子,如果你的业务是读取数值后要做计算,没有读锁的话,刚读到的数值可能在计算中途就被写线程修改,导致逻辑结果完全不符合预期。

2. 不使用锁读取Counter类的val会出现的问题

先针对你的假设逐一验证,再补充你可能遗漏的情况:

你的假设验证

  • 读取到非最新值:完全正确,而且情况可能比你想的更严重——不是“至少落后一个版本”,而是读线程可能永远看不到更新。因为没有读锁的内存屏障约束,JVM不会强制读线程从主内存刷新数据,它会一直复用本地工作内存的缓存值。
  • 读取到写入过程中的中间垃圾值:在Java中确实会发生!刚才也提到了,64位的long写操作可能被拆成两次32位写,读线程刚好在这两次写之间读取,就会得到一个高位新值、低位旧值(或者反过来)的错误数值。
  • 关于WriteLock的内存屏障:这里你有点误解——WriteLock的解锁操作确实会把写线程的工作内存数据刷新到主内存,但读线程如果不用读锁(或volatile等同步手段),没有强制从主内存读取的语义,依然会读取本地缓存的旧值,所以还是存在看不到写线程修改的风险。

你遗漏的其他情况

  • 指令重排序导致的逻辑混乱:JVM在没有同步约束的情况下,可能对读取指令和后续业务逻辑指令进行重排序。比如,读线程读取val之后,本该基于这个值做的计算,可能被提前到读取之前执行,导致逻辑完全错乱。
  • 多变量读取的一致性问题:如果后续给Counter类增加关联变量(比如同时维护count和totalAmount),没有读锁的话,可能出现读到count是最新值,但totalAmount还是旧值的情况,破坏了数据的逻辑一致性。读锁能保证在整个读操作期间,所有共享数据都是一个一致的快照。

内容的提问来源于stack exchange,提问作者user6412004

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:12:49