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

Java并发ReadWriteLock性能测试:读写锁为何慢于全写锁?

为什么你的读写锁测试结果完全反常

你测出来「分离读写锁比全用写锁慢近5倍」,根本不是读写锁的设计有问题,是两个现实因素叠出来的结果:你的测试场景刚好踩中了读写锁的性能最差区间,加上测试代码本身的设计bug,完全把读写锁的优势给磨没了。

核心原因拆解

1. 你的写操作频率高到彻底废掉了读写锁的并发优势

读写锁的设计初衷,从来不是在写密集场景下提性能——它的核心收益是读远多于写、两次写之间有充足的读空窗时,多个读线程可以并行拿读锁互不阻塞,省掉普通排他锁的串行等待成本。
但你设置的写任务是scheduleAtFixedRate(r2, 1, 1, TimeUnit.MICROSECONDS),也就是每1微秒触发一次写。这个粒度已经细到离谱了:现代操作系统的定时器精度、线程调度的最小切换成本都在100微秒以上,你设1微秒的间隔,实际跑起来的效果就是写任务根本停不下来,队列里永远有写请求等着抢锁,根本没有给读操作留出可以共享读、不需要等写的空窗期。

2. JDK读写锁的防写饥饿机制,在这个场景下是纯性能负担

你用的ReentrantReadWriteLock不管公平还是非公平模式,都有一个核心设计:为了防止连续到来的读请求把写请求永远堵在队列里(也就是写饥饿),只要同步队列里有等待写锁的线程,新来的读锁请求就不能直接插队拿锁,必须等前面的写锁拿到、完全释放后,后面的读线程才能一起进入临界区。
放到你这个测试场景里,流程就变成了死循环:

  • 10个读线程疯狂想进临界区读数据,刚准备拿读锁,就发现队列里有写请求在排队→读线程全部阻塞挂起
  • 写线程拿到锁,把数组值改一下,释放锁
  • 这时候所有挂起的读线程被唤醒,一起冲进去拿读锁,刚读个没两次,下一个写请求又排到了队列队首
  • 读线程又被全部挂起,等下一次写锁释放
    这一套反复「阻塞-唤醒-再阻塞」的内核上下文切换开销,比直接让所有线程串行拿写锁的成本高太多了——毕竟全用写锁的时候,所有线程就是按顺序排队拿锁,锁的持有时间就是读个数组元素的几纳秒,拿完就放,下一个线程直接上,没有反复挂起唤醒的额外开销。

3. 你的测试本身有多个会引入严重偏差的设计问题

  • 读操作的临界区太轻了:你拿到读锁之后,就只读一个int值,这个操作本身的执行时间是几纳秒,拿锁、解锁的固有开销都比这个操作本身大100倍。就算读锁能并行,多线程并行读的那点收益,还抵不过ReentrantReadWriteLock内部维护读锁计数、状态位的额外开销——相比之下,你全用的那个排他写锁,实现是极短的CAS路径,没有额外状态维护成本,自然速度快。
  • 计时边界完全错位:你启动了定时写任务,但从头到尾都没有手动关闭这个定时线程池。意味着所有读线程跑完、你join完开始算时间的时候,那个写任务还在后台每1微秒疯狂触发,持续占CPU抢锁,完全干扰读线程的运行节奏。
  • 根本没有触发读写锁的核心并行收益:因为写请求太密集,读线程之间从来没有真正并行进过临界区,每次读都是被写请求打断、排队,和排他锁的串行流程没有本质区别,反而多了读写锁本身的状态维护和上下文切换成本。

怎么改才能测出读写锁的真实性能

  • 把写操作的频率降下来:比如改成每100毫秒甚至1秒触发一次写,让99%的时间里系统只有读线程在跑,这时候才能看到多个读线程并行拿读锁、不互相阻塞的优势。
  • 把读操作的临界区稍微做“重”一点:比如拿到读锁之后,多做几次循环计算、或者把读到的值复制到本地变量做几次校验,拉长读锁的持有时间,这样并行读的收益才能盖过锁本身的维护开销。
  • 所有测试线程运行结束后,第一时间手动关闭那个定时线程池,别让后台任务干扰测试结果。
  • 可以对比下公平模式和非公平模式下的性能差异:公平模式下锁的获取严格按排队顺序,非公平模式下允许读线程在空窗期插队拿锁,不同场景下的性能表现差很多。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.24 19:15:09