Reader-Writer多线程实现问题:Writer线程无法穿插Reader执行
读写者问题代码分析:Writer线程无法穿插执行的原因
问题描述
我是多线程开发新手,正在尝试解决读写者(Reader-Writer)问题,编写了如下Java代码,但运行时所有Reader线程先执行完毕,Writer线程只能最后执行,无法穿插在Reader线程之间。
代码实现
public class ReaderWriterProblem implements Runnable { Semaphore readerSemaphore; Semaphore writerSemaphore; static int currentReadingThreads = 0; public ReaderWriterProblem(Semaphore readerSemaphore, Semaphore writerSemaphore) { this.readerSemaphore = readerSemaphore; this.writerSemaphore = writerSemaphore; } public void enterCriticalSection() throws InterruptedException { if(!Thread.currentThread().getName().startsWith("Reader")) { writerSemaphore.acquire(); System.out.println(Thread.currentThread().getName()+" acquired lock"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" going to release lock"); readerSemaphore.release(); writerSemaphore.release(); } if(Thread.currentThread().getName().startsWith("Reader")) { if(currentReadingThreads == 0) readerSemaphore.acquire(); currentReadingThreads = currentReadingThreads + 1; System.out.println(Thread.currentThread().getName()+" in critical section"); Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+" going to leave critical section"); currentReadingThreads = currentReadingThreads - 1; if(currentReadingThreads == 0) { readerSemaphore.release(); writerSemaphore.release(); } } } @Override public void run() { try { enterCriticalSection(); } catch(InterruptedException e) { e.printStackTrace(); } } public static void main(String args[]) { Semaphore readerSemaphore = new Semaphore(1); Semaphore writerSemaphore = new Semaphore(0); int readerThreadCounter = 0, writerThreadCounter = 0; for(int i = 0; i < 10; i++) { Thread readerThread1 = new Thread(new ReaderWriterProblem(readerSemaphore, writerSemaphore), "Reader Thread "+readerThreadCounter); readerThreadCounter = readerThreadCounter + 1; Thread readerThread2 = new Thread(new ReaderWriterProblem(readerSemaphore, writerSemaphore), "Reader Thread "+readerThreadCounter); readerThreadCounter = readerThreadCounter + 1; Thread writerThread1 = new Thread(new ReaderWriterProblem(readerSemaphore, writerSemaphore), "Writer Thread "+readerThreadCounter); readerThreadCounter = readerThreadCounter + 1; Thread writerThread2 = new Thread(new ReaderWriterProblem(readerSemaphore, writerSemaphore), "Writer Thread "+readerThreadCounter); readerThreadCounter = readerThreadCounter + 1; Thread readerThread3 = new Thread(new ReaderWriterProblem(readerSemaphore, writerSemaphore), "Reader Thread "+readerThreadCounter); readerThreadCounter = readerThreadCounter + 1; readerThread1.start(); readerThread2.start(); writerThread1.start(); writerThread2.start(); readerThread3.start(); } } }
运行输出
Reader Thread 0 in critical section Reader Thread 6 in critical section . . . Reader Thread 0 going to leave critical section Reader Thread 6 going to leave critical section . . . Writer Thread 0 acquired lock Writer Thread 0 going to release lock Writer Thread 1 acquired lock Writer Thread 1 going to release lock . . . Writer Thread 19 acquired lock Writer Thread 19 going to release lock
问题原因分析
1. 信号量初始化逻辑错误
writerSemaphore初始化为Semaphore(0),意味着这个信号量一开始没有可用许可。Writer线程启动后执行writerSemaphore.acquire()会直接进入阻塞状态,完全没有机会参与锁竞争。只有当最后一个Reader线程执行完毕时,才会释放一次writerSemaphore,此时才会唤醒一个Writer线程,但后续Writer仍需等待,而Reader线程会持续抢占readerSemaphore,导致所有Reader先执行完毕。
2. 读写互斥的核心逻辑完全混乱
- Writer线程的释放逻辑错误:Writer获取
writerSemaphore后,执行完毕同时释放readerSemaphore和writerSemaphore,这会导致readerSemaphore的许可数异常增加,彻底破坏了信号量的互斥控制逻辑。 - 读者计数的线程安全问题:静态变量
currentReadingThreads没有任何同步保护(如synchronized或原子类),多个Reader线程同时修改这个变量会导致计数不准确,进而引发锁的释放时机错误。 - 读者进入的非原子操作:Reader线程中
if(currentReadingThreads == 0) readerSemaphore.acquire()的判断与锁获取操作不是原子的,可能出现多个Reader线程同时判断currentReadingThreads == 0,然后都去获取readerSemaphore,破坏了互斥性。 - 信号量依赖关系错误:正常读写者问题中,应该用一个互斥锁保护读者计数,用另一个信号量控制写者访问,而你代码中的两个信号量互相依赖的逻辑完全不符合读写锁的设计思路。
修正方向
- 最简单可靠的方式是直接使用Java自带的
ReentrantReadWriteLock,它已经实现了成熟的读写锁逻辑,无需手动管理信号量和计数。 - 如果坚持手动用信号量实现:
- 新增一个互斥信号量(如
mutex),用于保护currentReadingThreads的修改和判断操作,确保这些操作是原子的。 - 用
writeLock信号量控制写者的访问:第一个Reader进入时获取writeLock,最后一个Reader离开时释放writeLock;写者必须获取writeLock才能进入临界区,确保读写互斥。
- 新增一个互斥信号量(如
内容的提问来源于stack exchange,提问作者user3153356
相关产品推荐
相关产品推荐

