为何Peterson解法中线程共享静态变量不更新?添加空输出却正常
我在尝试用Peterson解法解决临界区问题时,发现共享静态变量无法在线程中正确更新,除非在循环里加上空的System.out.print("");语句。请问这一现象的原因是什么?
附Peterson.java代码如下:
public class Peterson { public static void main(String[] args) throws InterruptedException { Process p1 = new Process(0); Process p2 = new Process(1); p1.start(); p2.start(); p1.join(); p2.join(); } } class Process extends Thread { static int turn; static boolean[] wait = new boolean[2]; int ind; public Process(int ind) { this.ind = ind; } @Override public void run() { int ind2 = 1-ind; wait[ind] = true; turn = ind2; // USING THIS WORKS // while(wait[ind2] && turn == ind2) {System.out.print("");} // USING THIS DOESN'T WORK while(wait[ind2] && turn == ind2) {;} // Critical Section System.out.printf("%d Started%n", ind); for (long t = System.currentTimeMillis(); System.currentTimeMillis() - t < 100;); System.out.printf("%d Ended%n", ind); wait[ind] = false; } }
原因分析
1. Java内存模型的可见性缺失
Java的内存模型中,每个线程都拥有独立的工作内存,共享变量存储在主内存中。线程读取共享变量时会先将其拷贝到工作内存,修改后再写回主内存。但如果没有同步机制约束,JVM会进行优化:比如将共享变量缓存到工作内存,不及时从主内存刷新最新值,也不把本地修改同步回主内存。
你的代码里turn和wait都是共享静态变量,但空while循环没有任何同步操作,JVM会认为这些变量的值不会被其他线程修改,直接缓存到当前线程的工作内存中,导致线程无法感知另一个线程对这些变量的修改,从而一直卡在循环里无法退出。
2. System.out.print的同步副作用
System.out是PrintStream的实例,它的print方法内部用synchronized关键字实现了同步。当执行同步代码时,Java会触发内存屏障:
- 进入同步块前,线程会失效工作内存中的变量,强制从主内存读取最新值
- 退出同步块后,线程会将工作内存中的修改写回主内存,确保其他线程可见
哪怕是空的System.out.print("");,也会触发这个同步机制,让线程能正确读取到另一个线程修改后的wait和turn变量,从而退出循环。
3. 正确的解决方案
不要依赖System.out这种临时的hack方式,应该用Java原生的并发机制保证可见性:将共享变量声明为volatile,修改后的代码如下:
class Process extends Thread { static volatile int turn; static volatile boolean[] wait = new boolean[2]; // 其余代码不变 }
volatile关键字会强制线程每次读取变量都从主内存获取,修改后立即写回主内存,既保证了变量的可见性,又禁止了指令重排序,完全符合Peterson算法的内存语义要求。
内容的提问来源于stack exchange,提问作者John

