多线程卖票问题
多线程卖票问题
一、问题
假设共有固定数量的车票,开启多个线程模拟多个售票窗口同时售票,如果不做特殊处理,会出现同一张票被多次卖出、票数超卖的异常情况。
二、线程基础概念
线程是进程的运行子分支,同一个进程内的多个线程会共享进程中的数据。
线程安全问题的根源:比如多个线程同时修改同一份共享数据时,操作过程可能被 CPU 打断,最终导致数据结果出现问题。
三、创建线程的两种方式
方式 1:继承 Thread 类
自定义类继承 Thread 类
重写 run() 方法,编写线程要执行的业务逻辑
创建自定义类对象,调用 start() 方法启动线程
方式 2:实现 Runnable 接口
自定义类实现 Runnable 接口
重写 run() 方法,编写线程执行逻辑
将自定义类的对象传入 Thread 类的构造方法
调用 Thread 对象的 start() 方法启动线程
四、线程内存模型
堆内存:所有线程共享,静态变量、共享数据都存储在这里,比如总票数 count。
栈内存:每个线程私有,存放线程自己的局部变量。
线程操作共享数据时,会先把数据复制到自己的本地变量副本中,修改完成后再写回共享内存。普通的数值增减不是原子操作(执行中途可以被 CPU 打断),因此多线程同时操作就会出现数据错误。
线程内存模型示意图:
五、线程安全解决方案:synchronized 内置锁
作用
将一段代码包裹起来,保证同一时刻只有一个线程能执行这段代码,让操作变成不可打断的原子操作。尽可能避免出现比如同一张票被多次卖出、票数超卖的异常情况
语法
java
synchronized(锁对象) {
// 需要保证原子性的核心代码
}
注意点
锁对象必须是所有线程都能访问到的同一个对象,通常用静态对象充当全局锁。
锁的粒度要尽量小,只包裹核心的共享数据操作代码;锁的范围太大,会导致其他线程长时间等待,降低程序运行效率。
线程进入同步代码块时自动获取锁,代码执行完毕后自动释放锁。
六、卖票代码核心逻辑
定义静态的总票数 count 和锁对象 obj,保证所有线程共享同一份数据和锁。
每个售票线程循环售票,在修改票数前加锁,并且加锁后再次判断票数是否大于 0,防止超卖。
减票操作完成后再输出售票信息,保证输出的数据准确。
小结
卖票问题的核心是多线程共享数据的安全问题,通过 synchronized 内置锁可以保证售票操作的原子性,有效避免超卖、重复售票的异常。