Java多线程问题:学生线程重复读白板及修改后死锁排查
Java多线程同步问题:避免学生重复读取白板内容
我正在学习Java多线程,需要实现以下场景:
- 教师线程在白板写入文本后,通知所有学生线程读取;
- 所有学生读完后通知教师,教师继续写入新文本,直到写入“end”,所有线程终止。
提供的参考代码存在缺陷:无法避免同一学生线程重复读取同一文本,输出出现重复读取的情况。我尝试修改代码以阻止重复读取,但修改后所有线程陷入等待状态,请问哪里出错了?
参考代码
class WhiteBoard { String text; int numberOfStudents=0; int count=0; public void attendance() { numberOfStudents++; } synchronized public void write(String t) { System.out.println("Teacher is Writing " +t); while(count!=0) try{wait();}catch(Exception e){} text=t; count=numberOfStudents; notifyAll(); } synchronized public String read() { while(count==0) try{wait();}catch(Exception e){} String t=text; count--; if(count==0) notify(); return t; } } class Teacher extends Thread { WhiteBoard wb; String notes[]={"Java is language","It is OOPs","It is Platform Independent","It supports Thread","end"}; public Teacher(WhiteBoard w) { wb=w; } public void run() { for(int i=0;i<notes.length;i++) wb.write(notes[i]); } } class Student extends Thread { String name; WhiteBoard wb; public Student(String n,WhiteBoard w) { name=n; wb=w; } public void run() { String text; wb.attendance(); do { text=wb.read(); System.out.println(name + " Reading " + text); System.out.flush(); }while(!text.equals("end")); } } public class SCThread2 { public static void main(String[] args) { WhiteBoard wb=new WhiteBoard(); Teacher t=new Teacher(wb); Student s1=new Student("A",wb); Student s2=new Student("B",wb); Student s3=new Student("C",wb); Student s4=new Student("D",wb); t.start(); s1.start(); s2.start(); s3.start(); s4.start(); } }
参考代码输出
Teacher is Writing Java is language Teacher is Writing It is OOPs Teacher is Writing It is Platform Independent D Reading Java is language B Reading Java is language B Reading It is OOPs < Repeating C Reading Java is language C Reading It is OOPs Teacher is Writing It supports Thread D Reading It is OOPs B Reading It is OOPs < Repeating B Reading It is Platform Independent B Reading It is Platform Independent D Reading It is Platform Independent C Reading It is Platform Independent Teacher is Writing end C Reading It supports Thread < Repeating D Reading It supports Thread B Reading It supports Thread C Reading It supports Thread < Repeating D Reading end B Reading end C Reading end A Reading Java is language A Reading end
我的修改代码
import static java.lang.Thread.currentThread; class WhiteBoard { String text; int numberOfStudents = 0; int count = 0; String[] names; public void attendance() {numberOfStudents++;} synchronized public void arrayInit() { names = new String[numberOfStudents]; } synchronized public boolean hasRead(Thread t, int length) { String name = t.getName(); for(int i = 0; i < length; i++) { if(names[i] == name) { return true; } } register(currentThread()); return false; } synchronized public void register(Thread t) { String name = t.getName(); for(int i = 0; i < names.length; i++) { if(names[i] == null) { names[i] = name; break; } } } synchronized public void write(String text) { while (count != 0) { try {wait();} catch (Exception e) {System.out.println(e.getMessage());} } System.out.println("Teacher is writing " + text); this.text = text; count = numberOfStudents; notifyAll(); } synchronized public String read() { boolean hasRead = hasRead(currentThread(), count); while(count == 0 || hasRead == true) { try {wait();} catch (Exception e) {System.out.println(e.getMessage());} } String t = text; count--; if(count == 0) { arrayInit(); notify(); } return t; } } class Teacher extends Thread { WhiteBoard w; String[] notes = {"Java is language", "It is OOPs", "It is platform independent", "It supports Thread", "end"}; public Teacher(WhiteBoard w) { this.w = w; } public void run() { for(int i = 0; i < notes.length; i++) { w.write(notes[i]); } } } class Student extends Thread { WhiteBoard w; String name; public Student (WhiteBoard w, String name) { super(name); this.w = w; this.name = name; w.attendance(); } public void run() { String text; do { text = w.read(); System.out.println(name + " Reading " + text); System.out.flush(); } while (!text.equals("end")); } } public class SCThread2 { public static void main(String[] args) { WhiteBoard w = new WhiteBoard(); Teacher t = new Teacher(w); Student s1 = new Student(w, "John"); Student s2 = new Student(w,"Jonny"); Student s3 = new Student(w, "Janardan"); w.arrayInit(); t.start(); s1.start(); s2.start(); s3.start(); } }
错误原因分析
你的修改代码中存在几个关键问题,导致线程陷入永久等待:
hasRead逻辑未动态更新:在read方法中,hasRead值是循环前一次性获取的,当线程被notifyAll唤醒后,不会重新判断当前学生是否可以读取新内容,依然拿着旧的true值进入等待,永远无法退出循环。names数组重置时机错误:arrayInit在所有学生读完后重置数组,但此时部分学生可能还处于等待状态,它们没有机会更新自己的读取状态,会错误认为自己已经读过新内容。- 字符串比较风险:使用
==比较线程名称,虽然当前场景下可能正常,但这是不良习惯,应该用equals()比较字符串内容。
正确解决方案
我们可以通过轮次标记机制解决重复读取问题:给WhiteBoard添加轮次编号,每个学生记录自己已读取的轮次,只有当教师写入新轮次内容时,学生才会读取,彻底避免重复读取同一轮内容。
修正后的代码
class WhiteBoard { private String text; private int numberOfStudents = 0; private int studentsReadCount = 0; private int currentRound = 0; // 记录当前写入的轮次 public synchronized void attendance() { numberOfStudents++; } public synchronized void write(String t) { // 等待所有学生读完上一轮内容 while (studentsReadCount != 0) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } System.out.println("Teacher is Writing " + t); this.text = t; studentsReadCount = numberOfStudents; currentRound++; // 新轮次更新编号 notifyAll(); // 通知所有学生读取 } public synchronized String read() { int studentRound = currentRound; // 记录当前学生要读取的轮次 // 等待新轮次到来,且当前轮次还有未读取名额 while (currentRound == studentRound || studentsReadCount == 0) { try { wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return "end"; // 中断后返回结束标识 } } String content = this.text; studentsReadCount--; // 所有学生读完后通知教师继续写入 if (studentsReadCount == 0) { notify(); } return content; } } class Teacher extends Thread { private WhiteBoard wb; private String[] notes = {"Java is language", "It is OOPs", "It is Platform Independent", "It supports Thread", "end"}; public Teacher(WhiteBoard w) { this.wb = w; } @Override public void run() { for (String note : notes) { wb.write(note); if ("end".equals(note)) { break; } } } } class Student extends Thread { private String name; private WhiteBoard wb; public Student(String n, WhiteBoard w) { this.name = n; this.wb = w; } @Override public void run() { wb.attendance(); String text; do { text = wb.read(); System.out.println(name + " Reading " + text); System.out.flush(); } while (!"end".equals(text)); } } public class SCThread2 { public static void main(String[] args) { WhiteBoard wb = new WhiteBoard(); Teacher t = new Teacher(wb); Student s1 = new Student("A", wb); Student s2 = new Student("B", wb); Student s3 = new Student("C", wb); Student s4 = new Student("D", wb); t.start(); s1.start(); s2.start(); s3.start(); s4.start(); } }
修正说明
- 轮次标记机制:通过
currentRound和studentRound的对比,确保学生只读取自己未读过的新轮次内容,彻底解决重复读取问题。 - 同步逻辑优化:教师必须等待上一轮所有学生读完才写入新内容,学生必须等待新轮次到来才读取,保证线程间的有序协作。
- 异常处理改进:捕获
InterruptedException后中断线程,避免线程无限挂起,增强代码鲁棒性。
内容的提问来源于stack exchange,提问作者Painkiller
相关产品推荐
相关产品推荐

