导言
线程同步
是Java高并发
的核心内容之一,线程同步
目的是保证多线程以安全的方式按照一定的次序执行以满足对互斥资源的请求,从而协作完成某项任务,其重点是保证线程安全
。
所谓线程安全
,是多线程操纵共享数据的时候,对共享数据的操作不会出现丢失修改
、脏数据
等问题,或出现由于不满足可见性
、原子性
、有序性
而产生的问题。例如,线程A
让计数器counter
的值加1
的同时线程B
让计数器加1
,这就造成了丢失修改,与我们原本的目的向背驰:多线程对共享数据——计数器变量counter
的修改应该排队进行。
这里先不讨论丢失修改
、脏数据
以及可见性
等问题,因为这些是Java高并发
的永恒主题,在以后的博客当中,一定会进行深入地讨论。
多窗口售票
多窗口售票是一个入门级别的线程同步案例,也是线程同步和线程安全的重要应用,有助于理解多线程的原子性、可见性等问题。
多窗口售票的需求描述:
利用多线程模拟3个窗口售出300张票,三个窗口同时售票(注意:同时
指同一时间段,因此是并发
的意思,不是并行
),需要保证三个窗口不能出现错票
、重票
、漏票
的问题。
需求分析:
- 多线程同步,一定存在共享数据,在这个案例当中,共享数据就是一定数量的票,可以用一个静态变量表示,或者用同一个以票数为属性的对象表示。
- 对共享数据的操作需要保证互斥,因此必须使用
锁
,我们选择synchronized
来保证互斥访问共享数据,锁住的对象是可以是唯一的共享数据,或者其他唯一的对象也可以。 - 多线程的实现方式有多种,我们尝试其中两种:
继承Thread类
、实现Runnable接口
。
方式一:继承Thread类
继承Thread类
实现多线程的步骤是:
- 继承
Thread
类,重写run()
方法 - 创建子类对象,调用
start()
方法启动子线程
每个窗口对应一个线程,因此需要一个线程类。在这个线程类当中,需要一个静态变量表示多个线程的共享资源,即剩余票数。另外,可以给每个窗口一个独特的窗口名字,用普通的属性即可。
代码如下:
package com.java.www.day20210102;
/**
* 每个窗口代表1个线程,因此我们定义窗口类作为Thread的子类
* */
class WindowThread extends Thread{
/**静态变量是类变量,因此可以作为多个对象的共享数据,并进行初始化*/
private static int ticketNumber = 300;
/**窗口名称*/
private String windowName;
/**在构造方法当中传入窗口名称*/
public WindowThread(String windowName){
this.windowName = windowName;
}
/**重写run()方法*/
@Override
public void run() {
while (true){
// 每次循环都准备获取票号,获取之前都要先获取锁,锁只要是唯一的就可以(