为何这段Java并发代码运行一段时间后会卡住?
核心问题:错误的同步修饰引发锁竞争与阻塞
给isQueueEmpty和isQueueFull加synchronized后程序卡住,移除后恢复正常,本质是这两个同步方法破坏了生产者消费者模型的锁逻辑,具体原因如下:
锁竞争加剧导致线程无法执行核心操作
假设这两个检查方法属于TaskQueue类,它们的锁对象和push/pop方法是同一个(TaskQueue实例)。生产者(TaskGen)会先调用isQueueFull获取锁、检查队列、释放锁,再调用push重新抢锁;消费者(TaskExec)也是先调用isQueueEmpty抢锁检查,再调用pop抢锁执行。这种频繁的锁获取/释放会大幅提升锁竞争概率,极端情况下,生产者和消费者线程都卡在抢锁环节,无法完成入队/出队操作,程序自然停滞。等待逻辑的锁对象不匹配导致永久阻塞
如果你的代码里,在isQueueFull或isQueueEmpty内部调用了wait()(比如队列满时生产者在这方法里等,队列空时消费者在这里等),就会出现唤醒无效的问题:wait()和notify()必须绑定同一个锁对象。比如生产者在TaskGen实例的同步方法里调用wait(),但消费者在TaskQueue实例的同步方法里调用notify(),生产者线程永远收不到唤醒信号,会一直挂起,导致程序卡住。不必要的同步挤占核心操作的锁资源
就算没有等待逻辑,同步的检查方法会让线程频繁短暂持有锁,挤占push/pop的锁获取机会。比如消费者循环调用同步的isQueueEmpty,会持续占用TaskQueue的锁,生产者没法入队新任务,消费者也拿不到任务执行,最终输出卡住。
而移除synchronized后,检查方法不再占用锁,生产者和消费者的核心逻辑(push/pop)会通过自身的同步逻辑(在同一个锁块内完成检查、等待、执行、唤醒的完整流程)协调,避免了额外的锁竞争,程序就能正常运行。
内容的提问来源于stack exchange,提问作者Yogesh Tripathi

