Java多线程共享Request状态 移除打印语句后循环无法退出问题排查
问题根因
你遇到的是Java内存模型(JMM)定义的共享变量可见性问题:
- Java中每个线程都有独立的工作内存,默认会把用到的共享变量缓存到工作内存中,不会每次都主动去主内存读取最新值。
System.out.println是被synchronized修饰的同步方法,执行时会触发内存屏障,强制线程清空工作内存的缓存,从主内存读取最新的变量值,所以保留调试打印时可以正常读取到更新后的status。- 去掉打印语句后,JIT编译器会认为循环里没有修改
status的操作,直接优化为只读取一次status的值并复用缓存,导致永远读不到其他线程修改的最新状态,循环无法终止。
修复方案
1. 快速修复可见性问题
给RequestHandler类的status字段添加volatile修饰:
private volatile RequestStatus status = RequestStatus.NEW;
volatile会禁止JIT做指令重排优化,同时强制每次读取status都从主内存获取,写入时也直接同步到主内存,保证多线程之间的状态可见性。
也可以选择给getStatus()和setStatus()方法加synchronized修饰,同步方法同样会触发内存屏障保证可见性。
2. 优化自旋等待逻辑
你当前用的空自旋等待会大量浪费CPU资源,建议用CountDownLatch实现阻塞等待:
修改RequestHandler类,新增CountDownLatch成员:
private CountDownLatch finishLatch = new CountDownLatch(1);
在setStatus方法中状态变为SUCCESSFUL/FAILED时调用finishLatch.countDown();
logIn方法里的循环改为直接等待:
public User logIn(String name){ System.out.println("!Log in"); RequestHandler<User> request = new RequestHandler<>("logIn", name, out, results); requestQueue.addLast(request); try { request.getFinishLatch().await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return null; } if (request.getStatus() == RequestStatus.SUCCESSFUL) { return request.result(); } return null; }
3. 其他并发问题修复
- 你当前用
LinkedList作为请求队列,它不是线程安全的,多线程同时读写会出现并发异常,建议替换为ConcurrentLinkedQueue或者LinkedBlockingQueue。 request()方法里的while(!results.containsKey(commend)){}空自旋也存在同样的可见性和CPU浪费问题,也可以用CountDownLatch或者阻塞队列的特性优化。
内容的提问来源于stack exchange,提问作者Apsik
相关产品推荐
相关产品推荐

