Java使用继承Thread类与ReentrantLock售票为何出现0号票?
异常成因分析
- 核心根因是锁对象未被多线程共享,同步逻辑完全失效
代码中ReentrantLock是ThreadOne类的非静态实例属性,每创建一个ThreadOne对象就会生成一个独立的锁实例。main方法中分别实例化了t1、t2两个ThreadOne对象作为两个售票窗口,两个窗口持有的是完全不同的锁对象,不存在互斥关系——这是使用显式锁时非常常见的误区:同步生效的核心前提是多线程争抢同一个锁实例,仅调用lock方法本身不会产生互斥效果,相当于两个窗口各自带了独立的锁,完全不会拦截对方进入售票逻辑,自然会出现竞态条件。 - 触发售卖0号票的具体时序过程:
当余票仅剩1张时,两个线程因为持有的锁独立,可以同时进入售票判断分支:
- 两个线程同时读取到共享的
ticket=1,判定满足ticket>0的售票条件 - 两个线程先后执行
sleep模拟售票耗时,Window 2 先醒,打印"卖出1号票",并将ticket值更新为0 - Window 1 随后睡醒,没有重新判断余票状态,直接读取当前已经被改成0的
ticket值打印,随后将ticket减为-1,最终出现售卖0号票的异常输出。
- 两个线程同时读取到共享的
对应解决方法
- 调整锁的作用域,让锁成为所有线程共享的类级别静态属性,和同样是静态共享的
ticket属性保持一致的作用粒度:
将原代码中定义的实例锁:
修改为静态共享锁,同时加private ReentrantLock lock = new ReentrantLock();final修饰避免锁实例被意外修改:private static final ReentrantLock lock = new ReentrantLock(); - 调整后的逻辑验证:所有售票线程会争抢同一个锁对象,同一时刻只会有一个线程进入临界区执行售票逻辑。当某一个线程持锁进入临界区时,会完整执行「判断余票→打印售票信息→扣减余票」的全流程,执行完再释放锁;其他线程拿到锁后会重新判断余票值,当余票为0时直接跳出循环,不会进入打印流程,从根源上避免售卖0号票的问题。
内容的提问来源于stack exchange,提问作者CarterZhang
相关产品推荐
相关产品推荐

