Java线程无法正确从List移除自身致程序冻结?求排查修复
问题原因分析
- 非线程安全集合的并发损坏:如果你的
passengersOnBoard使用了ArrayList这类非线程安全集合,多线程下的add/remove/set操作会破坏集合内部结构(比如扩容时的数组拷贝、元素移位错误),直接产生null元素。 - 原子性操作缺失:乘客下船的核心逻辑(从列表移除自身+状态同步)没有作为原子操作执行。比如乘客先将列表中自身位置设为
null,还未完成移除,BoatKeeper就开始遍历检查,误将null当作未下船的乘客,进入无限等待。 - 调试时序掩盖问题:调试时的断点会强制线程暂停,让原本混乱的并发操作变得有序,竞态条件无法触发,因此问题“消失”。
修复方案
1. 替换为线程安全集合
将非线程安全的列表替换为CopyOnWriteArrayList(适合读多写少场景)或ConcurrentLinkedQueue(适合队列操作场景),这类集合原生处理并发修改,避免结构损坏:
// 替换原ArrayList定义 private List<Passenger> passengersOnBoard = new CopyOnWriteArrayList<>();
2. 保证核心操作的原子性
对乘客上下船、BoatKeeper检查列表的操作加锁,确保“修改集合+状态通知”是原子执行的,避免中间状态被读取:
// 乘客下船方法 public void disembark() { synchronized (passengersOnBoard) { passengersOnBoard.remove(this); // 通知BoatKeeper状态变化 passengersOnBoard.notifyAll(); } } // BoatKeeper等待下船完成的逻辑 public void waitForAllDisembark() throws InterruptedException { synchronized (passengersOnBoard) { while (!passengersOnBoard.isEmpty()) { passengersOnBoard.wait(); } } }
3. 修正Semaphore与乘客状态的绑定逻辑
确保Semaphore的许可数量与实际在船乘客严格对应:
- 乘客上船前必须获取Semaphore许可,获取成功后再加入集合;
- 乘客下船后必须先从集合移除,再释放Semaphore许可;
- BoatKeeper的等待逻辑可以结合Semaphore的可用许可(
availablePermits())+ 集合状态双重校验,避免单一条件判断的漏洞。
4. 禁止用null标记已下船乘客
不要用null作为乘客已下船的占位符,直接从集合中移除对应乘客对象,或给Passenger类添加isOnBoard布尔字段,BoatKeeper通过字段状态判断,而非检查null。
内容的提问来源于stack exchange,提问作者Sergey Zolotarev
相关产品推荐
相关产品推荐

