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

基于ReentrantLock实现多读独占写同步器的问题及调试咨询

问题分析与解决方案

原代码的核心问题

  1. 忙等的低效与竞态风险:getDiff里的while (writeLock.isLocked());是无意义的忙等,既浪费CPU资源,还存在竞态条件——刚检查完写锁未被持有,线程切换后写线程立刻获取写锁,此时读线程会直接读取x和y,得到不一致的中间值。
  2. 死锁隐患:update方法先拿写锁再拿读锁,而getDiff方法可能持有读锁后等待写锁释放,一旦读线程持有读锁、写线程持有写锁,双方会互相等待对方的锁,直接触发死锁。
  3. 读重入逻辑错误:!readLock.isLocked()判断的是整个锁是否被持有,而非当前线程是否持有锁。如果其他线程持有读锁,当前线程会跳过锁获取直接进入后续逻辑,导致共享变量x、y的可见性无法保证。

正确的多读独占写实现

下面是基于ReentrantLock和Condition的实现,避免忙等、死锁,保证多读并发与写独占:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class CustomReadWriteSync {
    private int x = 0;
    private int y = 0;

    // 写锁:保证写操作独占,同时保护读计数的修改
    private final ReentrantLock writeLock = new ReentrantLock();
    // 条件变量:让写线程等待所有读线程完成
    private final Condition readFinish = writeLock.newCondition();
    // 记录当前活跃的读线程数
    private int readCount = 0;

    public int getDiff() {
        // 1. 获取写锁,增加读计数(此时写线程无法进入)
        writeLock.lock();
        try {
            readCount++;
        } finally {
            writeLock.unlock();
        }

        try {
            // 读操作:多个读线程可同时执行,共享变量可见性由锁保证
            return Math.abs(x - y);
        } finally {
            // 2. 再次获取写锁,减少读计数,读完成时唤醒等待的写线程
            writeLock.lock();
            try {
                readCount--;
                if (readCount == 0) {
                    readFinish.signal();
                }
            } finally {
                writeLock.unlock();
            }
        }
    }

    public void update(int dx, int dy) {
        writeLock.lock();
        try {
            // 等待所有读线程执行完毕,避免写操作打断读
            while (readCount > 0) {
                readFinish.await();
            }
            // 写操作:独占执行,保证x和y的原子更新
            x += dx;
            y += dy;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            writeLock.unlock();
        }
    }
}

调试方法

1. 竞态条件与逻辑验证

  • 日志追踪:在getDiff的进入/退出、update的进入/退出位置,打印线程ID、readCount、x、y的值,直观查看线程执行顺序与变量变化。
  • 延迟模拟:在关键锁操作后插入Thread.sleep(100),人为制造线程切换,验证是否出现数据不一致的情况。

2. 死锁排查

  • 使用JDK自带的jstack命令:运行程序后,执行jstack <进程ID>,查看线程堆栈。如果存在死锁,会明确标记DEADLOCK状态,并显示线程等待的锁对象。

3. 忙等问题验证

  • 使用VisualVM工具:监控程序CPU使用率,原代码的忙等会导致CPU占用居高不下;替换为Condition实现后,CPU使用率会明显下降。

4. 单元测试验证

编写多线程测试用例,比如启动10个读线程循环调用getDiff,2个写线程循环调用update,验证读操作始终能获取到x和y的正确差值,不会出现中间态的错误结果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 14:20:57