为何5线程环境下仅两个线程轮流调用synchronized方法?
问题原因分析
咱们先把两种写法的执行逻辑拆解开,就能明白为啥会出现只有两个线程轮流干活的情况了:
错误写法的核心问题:同步范围过大
不管是用synchronized方法,还是用synchronized块包裹整个while循环,本质都是让线程一旦拿到锁,就会在同步块内的循环里反复执行,直到调用wait()释放锁。具体流程是这样的:
- 初始5个线程同时抢锁,依次有线程5、1、2、3、4拿到锁,各执行一次售票逻辑,然后调用
wait()释放锁,全部进入等待队列(Wait Set)。 - 最后执行的线程4调用
notifyAll(),把所有等待队列里的线程都唤醒到锁的竞争队列(Entry Set),然后自己也进入等待队列。 - 这时候竞争队列里的线程开始抢锁,假设线程3抢到了——它会直接回到
while循环的下一轮,执行售票、notifyAll()、sleep()、wait(),再次释放锁进入等待队列。 - 接下来锁释放时,线程4因为刚被唤醒,线程调度器大概率会优先调度它,导致线程3和4开始轮流抢锁、执行,剩下的线程1、2、5永远挤不进去,只能一直等。
简单说:这种写法下,抢到过锁的线程被唤醒后,会直接回到循环里继续抢锁,相当于“插队”了,其他线程根本没机会竞争。
正确写法的关键:每次循环重新竞争锁
把synchronized块放在while循环内部,相当于每次售票都要重新排队抢锁:
- 线程执行完
wait()释放锁后,会退出同步块,回到while循环的条件判断。 - 当它再次尝试进入同步块时,会重新加入到锁的竞争队列尾部,和其他所有等待的线程公平竞争。
- 线程调度器这时候就能随机选择不同的线程,保证所有线程都有机会执行售票逻辑,也就出现了你想要的轮流执行效果。
额外提两个细节
Thread.sleep(10)不会释放锁,在同步块里执行会拉长线程持有锁的时间,但不是导致问题的核心原因。System.exit(0)放在同步块内时,只要有一个线程执行到这行,整个程序直接终止;正确写法里每个线程都会先检查ticketsLeft,票卖完后所有线程都会退出循环,最终由某个线程触发退出。
内容的提问来源于stack exchange,提问作者Chingfung Yung
相关产品推荐
相关产品推荐

