基于ReentrantLock实现多读独占写同步器的问题及调试咨询
问题分析与解决方案
原代码的核心问题
- 忙等的低效与竞态风险:
getDiff里的while (writeLock.isLocked());是无意义的忙等,既浪费CPU资源,还存在竞态条件——刚检查完写锁未被持有,线程切换后写线程立刻获取写锁,此时读线程会直接读取x和y,得到不一致的中间值。 - 死锁隐患:
update方法先拿写锁再拿读锁,而getDiff方法可能持有读锁后等待写锁释放,一旦读线程持有读锁、写线程持有写锁,双方会互相等待对方的锁,直接触发死锁。 - 读重入逻辑错误:
!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
相关产品推荐
相关产品推荐

