Java非公平ReentrantReadWriteLock工作原理及公平/非公平模式差异场景咨询
为什么你的ReentrantReadWriteLock公平/非公平模式输出一致?
嘿,我来帮你理清这个问题!你当前的测试场景其实没触碰到ReentrantReadWriteLock两种模式的核心差异点,所以才会看到输出完全一致。咱们一步步拆解:
你的测试场景问题所在
看你的线程启动和时序:
reader2(val=1)先启动,但它会先sleep 3秒,之后才去抢读锁writer1启动后sleep 1秒,然后去抢写锁——这时候reader1已经持有读锁了(reader1启动后直接抢锁,没有sleep),所以writer1会进入等待队列reader23秒后醒来抢锁时,writer1已经在等待队列里排队了
不管是公平还是非公平模式,当队列里已经有写线程在等待时,新的读请求都会被阻塞,必须等写线程执行完才能获取读锁——这是ReentrantReadWriteLock为了避免写线程饥饿的设计,所以你的两种模式输出自然一致。
能复现差异的核心场景
要看到两种模式的区别,你需要构造这样的场景:
当有读线程持有读锁时,先启动一个写线程(它会进入等待队列),然后再启动多个新的读线程。此时:
- 公平模式:新的读线程必须加入等待队列,排在写线程之后,等写线程执行完才能获取读锁
- 非公平模式:新的读线程可以直接插队,和当前持有读锁的线程共享读锁,无需等待写线程
修改后的测试代码
我调整了你的代码,让它能复现这个差异:
package lockTest; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyLockTest { // 这里可以切换true(公平)/false(非公平) static private ReadWriteLock myLock = new ReentrantReadWriteLock(false); public class Reader extends Thread { private final String name; public Reader(String name) { this.name = name; } public void run() { myLock.readLock().lock(); try { System.out.println("Reader [" + name + "] 进入临界区"); // 持有读锁2秒,模拟业务操作 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("Reader [" + name + "] 退出临界区"); myLock.readLock().unlock(); } } } public class Writer extends Thread { private final String name; public Writer(String name) { this.name = name; } public void run() { myLock.writeLock().lock(); try { System.out.println("Writer [" + name + "] 进入临界区 *****"); // 持有写锁2秒,模拟业务操作 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("Writer [" + name + "] 退出临界区 *****"); myLock.writeLock().unlock(); } } } public static void main(String[] args) throws InterruptedException { MyLockTest test1 = new MyLockTest(); // 第一步:启动第一个读线程,让它持有读锁 Reader reader1 = test1.new Reader("R1"); reader1.start(); // 确保reader1先拿到读锁 Thread.sleep(500); // 第二步:启动写线程,它会进入等待队列 Writer writer1 = test1.new Writer("W1"); writer1.start(); // 第三步:连续启动多个新的读线程 Reader reader2 = test1.new Reader("R2"); Reader reader3 = test1.new Reader("R3"); reader2.start(); reader3.start(); } }
两种模式的输出差异
非公平模式(new ReentrantReadWriteLock(false))输出:
Reader [R1] 进入临界区 Reader [R2] 进入临界区 Reader [R3] 进入临界区 Reader [R1] 退出临界区 Reader [R2] 退出临界区 Reader [R3] 退出临界区 Writer [W1] 进入临界区 ***** Writer [W1] 退出临界区 *****
可以看到,R2、R3直接插队获取了读锁,和R1共享,完全无视等待中的W1。
公平模式(new ReentrantReadWriteLock(true))输出:
Reader [R1] 进入临界区 Reader [R1] 退出临界区 Writer [W1] 进入临界区 ***** Writer [W1] 退出临界区 ***** Reader [R2] 进入临界区 Reader [R3] 进入临界区 Reader [R2] 退出临界区 Reader [R3] 退出临界区
公平模式严格按照请求顺序执行:R1先执行,然后是等待队列里的W1,最后才是R2、R3。
总结两种模式的核心区别
- 公平模式:严格遵循线程请求锁的先后顺序,队列里的线程必须按顺序获取锁,不会出现插队情况,能避免饥饿,但吞吐量会低一些。
- 非公平模式:允许新的读请求插队获取读锁(即使队列里有写线程等待),写锁释放时也允许新的请求插队,吞吐量更高,但可能导致写线程长时间饥饿(一直有读请求插队,写线程永远得不到执行)。
内容的提问来源于stack exchange,提问作者rodolk
相关产品推荐
相关产品推荐

