Java并发:我的读写者问题解决方案为何无法正常工作?
解决读写者问题中写线程无法修改文件内容的问题
看起来你在实现经典读写者问题时踩了几个线程同步的坑,导致写线程要么无法执行修改操作,要么修改内容不符合预期。我来帮你逐一排查并修复这些问题:
核心问题分析
- 等待线程无法被唤醒:你在
startReading和startWriting里用了wait()让线程阻塞,但完全没有对应的notifyAll()来唤醒它们。比如当所有读线程结束后,写线程会一直卡在while(readCount > 0)的循环里,永远不知道可以继续执行了。 - 写操作硬编码内容:你的
startWriting方法里直接写死了file = file + "hi";,完全忽略了传入的edit参数,这就是为什么你看不到预期的写内容。 - 信号量使用冗余且有泄漏风险:已经用
synchronized修饰了方法,相当于已经保证了方法的互斥性,再叠加Semaphore属于重复控制;而且如果semWriter.acquire()后抛出异常,信号量没被释放,后续写线程永远拿不到权限。 - 状态变更未通知其他线程:
readCount或writeCount变化后,没有通知等待的线程,导致它们无法感知状态变化,一直处于阻塞状态。
修复后的完整代码
import java.util.Random; public class Main { public static void main(String[] args) { File file = new File("1. Chapter: "); Writer w1 = new Writer(file, " w1 "); Writer w2 = new Writer(file, " w2 "); Reader r1 = new Reader(file); Reader r2 = new Reader(file); Reader r3 = new Reader(file); Reader r4 = new Reader(file); Reader r5 = new Reader(file); w1.start(); w2.start(); r1.start(); r2.start(); r3.start(); r4.start(); r5.start(); try { w2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-> Final output: " + file.getFile()); } } class File { private String file; private int readCount; private int writeCount; public File(String file) { this.file = file; readCount = 0; writeCount = 0; } public synchronized void startReading() throws InterruptedException { // 等待所有写操作完成,用while避免虚假唤醒 while (writeCount > 0) { wait(); } readCount++; System.out.println(" --- File was read"); } public synchronized String endReading() { String temp = file; readCount--; // 通知等待的写线程:读线程数量减少了 notifyAll(); return temp; } public synchronized void startWriting(String edit) throws InterruptedException { // 等待所有读操作完成 while (readCount > 0) { wait(); } writeCount++; System.out.println(" --- File got edited"); // 使用传入的edit参数修改内容,不再硬编码 file = file + edit; } public synchronized void endWriting() { writeCount--; // 通知等待的读线程:写操作完成了 notifyAll(); } // 封装file变量,提供安全的获取方法 public synchronized String getFile() { return file; } } class Writer extends Thread { private File file; private String edit; public Writer(File file, String edit) { this.file = file; this.edit = edit; } @Override public void run() { Random rand = new Random(); try { sleep(1000); System.out.println(">W: " + Thread.currentThread().getName() + " started first write."); file.startWriting(" first" + edit); sleep(3000); System.out.println(">W: " + Thread.currentThread().getName() + " ended first write."); file.endWriting(); sleep(2000); System.out.println(">W: " + Thread.currentThread().getName() + " started second write."); file.startWriting(" second" + edit); sleep(3000); System.out.println(">W: " + Thread.currentThread().getName() + " ended second write."); file.endWriting(); System.out.println(">W: " + Thread.currentThread().getName() + " finished"); } catch (InterruptedException e) { e.printStackTrace(); } } } class Reader extends Thread { private File file; public Reader(File file) { this.file = file; } @Override public void run() { Random rand = new Random(); try { sleep(rand.nextInt(2000)); System.out.println(">R: " + Thread.currentThread().getName() + " started first read."); file.startReading(); sleep(3000); System.out.print(">R: " + Thread.currentThread().getName() + " ended first read: "); System.out.println(file.endReading()); sleep(rand.nextInt(2000)); System.out.println(">R: " + Thread.currentThread().getName() + " started second read."); file.startReading(); sleep(3000); System.out.print(">R: " + Thread.currentThread().getName() + " ended second read: "); System.out.println(file.endReading()); System.out.println(">R: " + Thread.currentThread().getName() + " finished"); } catch (InterruptedException e) { e.printStackTrace(); } } }
关键修改说明
- 移除冗余的Semaphore:
synchronized方法已经保证了方法的原子性和互斥性,不需要再用信号量控制计数变量的修改,简化了逻辑。 - 添加
notifyAll()唤醒等待线程:- 读线程结束时调用
notifyAll(),告诉等待的写线程可以尝试获取权限了。 - 写线程结束时调用
notifyAll(),告诉等待的读线程可以继续执行了。
- 读线程结束时调用
- 修复写内容硬编码问题:把
file = file + "hi";改成file = file + edit;,让写线程使用传入的参数修改内容。 - 封装成员变量:将
file设为私有,提供getFile()方法获取内容,符合面向对象封装原则,也避免直接访问带来的线程安全隐患。 - 用
while循环判断等待条件:这是线程等待的最佳实践,防止线程被虚假唤醒后直接执行后续逻辑。 - 修正拼写错误:把日志里的
startet改成started,输出更规范。
内容的提问来源于stack exchange,提问作者Coldvirus
相关产品推荐
相关产品推荐

