经典案例:卖票问题【线程同步】
一、案例需求
某个电影院上映了史诗级大片《回村的诱惑》,共有100张票,卖票窗口总共有三个。请设计一个程序模拟卖票的过程!
二、步骤分析
1、定义一个卖票的线程类实现Runnable接口,并且声明一个成员变量:private int ticket=100;
2、在run方法中,判断该票数是否大于0,就卖票,打印输出剩余票数和窗口;
3、一个线程执行完之后,票数减一;
4、如果票数<=0则停止售票;
5、创建一个测试类,在main方法中创建卖票类的对象;
6、创建三个Thread类,并将卖票类的对象传入。分别调用start()方法。
三、代码实现
SellTicket类:
public class SellTicet implements Runnable{private int ticket = 100;@Overridepublic void run() {while (true){if (ticket<=0){break;}else{ticket--;System.out.println(Thread.currentThread().getName() + "出票成功,剩余:"+ticket+" 张票");}}}
}
测试类:
public class DemoTest {public static void main(String[] args) {SellTicet sellTicet = new SellTicet();Thread t1 = new Thread(sellTicet);Thread t2 = new Thread(sellTicet);Thread t3 = new Thread(sellTicet);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}
输出结果(部分截图):
输出结果如上图所示,这似乎是非常完美的,当然由于Java的线程是抢占式的,所以输出的结果没有按照顺序来,所以结果是乱的。但是呢,现实生活中,出票也是需要时间的,所以我们就给线程增加一个睡眠时间去模拟这一情况。改进地方如下图所示
这个时候,输出结果就会出现非常惊人的效果:
输出结果出现了重复票,并且出现了负数票的情况。
四、问题分析
由于private int ticket=100 是一个共享数据。当多个线程访问下面这段代码块的时候。由于线程进入之后,都会休眠100ms,那么三个线程依次进来之后,就都会处于线程中的计时等待状态。当到了指定的时间之后,线程依次进行ticket--操作。
负票情况(假设此时ticket=1):
假如“窗口一”线程执行了这行代码(ticket=0),但是没有打印出来,这时,“窗口二”线程也执行了这行代码(ticket=-1),这个时候窗口一抢到CPU执行权了,就从控制台打印了-1张票这种情况了。
同票情况(假设此时ticket=10):
假如“窗口一”线程执行了这行代码(ticket=9),但是没有打印出来,这时,“窗口二”线程也执行了这行代码(ticket=8),这个时候窗口一抢到CPU执行权了,就从控制台打印了8张票,此时窗口二也抢到了CPU执行权,也从控制台打印了8张票这种情况了。
if (ticket<=0){break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() + "出票成功,剩余:"+ticket+" 张票");}
五、同步代码块的解决方案
出现了这种问题的原因:多个线程并发的访问同一块代码。
解决方案:把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
具体代码实现:synchronized关键字
我们将刚才的SellTicket类进行改造,具体代码如下:
public class SellTicet implements Runnable{private int ticket = 100;@Overridepublic void run() {while (true){synchronized (this){if (ticket<=0){break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() + "出票成功,剩余:"+ticket+" 张票");}}}}
}
测试类不变,输出结果如下:
此时,就不会出现相同票数和负数票数的问题了。
六、同步代码块的利弊
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
七、Lock锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。但需要注意的是:Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
我们把上面的SellTicket类再进行改造:
import java.util.concurrent.locks.ReentrantLock;public class SellTicet implements Runnable{private int ticket = 100;private ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true){lock.lock();if (ticket<=0){break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}ticket--;System.out.println(Thread.currentThread().getName() + "出票成功,剩余:"+ticket+" 张票");}lock.unlock();}}}
测试类不变,结果与上图结果一致。
但是由于我们是手动去加锁和释放锁。难免出现如果锁还没得及释放,加锁的代码中出现了异常,那么程序就会进入阻塞状态,所以我们最好把释放锁放在finally的地方,这样子代码就显得比较优雅了。
@Overridepublic void run() {while (true){lock.lock();try {if (ticket<=0){break;}else{Thread.sleep(100);ticket--;System.out.println(Thread.currentThread().getName() + "出票成功,剩余:"+ticket+" 张票");}} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}
好了,这就是线程同步问题中最经典的案例,感谢你的阅读!
经典案例:卖票问题【线程同步】相关推荐
- 线程安全问题经典案例---卖票
在入门多线程的时候,看到过不少的案例,其中卖票案例尤为经典,在这里自己也记录一下,同时加深对于线程安全的理解: 案例场景 情景一: 现在有一个电影院,马上要上映电影<战狼5>,电 ...
- 多线程经典问题 卖票问题
/** 需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票 思路: ① 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:pri ...
- DHV展示故事经典 案例 卖猪借宿
一男赶集卖猪,天黑遇雨,二十头猪(DHV展示)未卖成,到一农家借宿. SF说:家里只一人不便. (升起防护罩) 男:求你了大妹子,给猪一头. (类似假性时间限制解除防 ...
- 【Java 并发编程】多线程、线程同步、死锁、线程间通信(生产者消费者模型)、可重入锁、线程池
并发编程(Concurrent Programming) 进程(Process).线程(Thread).线程的串行 多线程 多线程的原理 多线程的优缺点 Java并发编程 默认线程 开启新线程 `Ru ...
- 数仓工具—Hive实战之full join 经典案例(13)
full join 经典案例 full join 增量数据同步更新 我们知道我们的数仓数据很大一部分是来自业务数据库的,那么这个时候我们数据同步的方式有两种一种是增量同步一种是全量同步,那么这个时候我 ...
- 多线程:线程同步与死锁(卖票案例)、线程通信、生产者与消费者
卖票案例 5个窗口同时卖票: 使用Runnable接口,只创建了一个ticket1对象,5个线程共享此对象,实现了资源共享. public class ticket1 implements Runna ...
- 线程同步(卖票案例)
1 卖票[应用] 案例需求 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票 实现步骤 在类中重写run()方法实现卖票,代码步骤如下 判断票数大于0, ...
- 【Java9】异常,finally,线程创建(卖票),线程同步(卖包子),线程练习
文章目录 1.错误和异常区别:Arrays.toString(array) 2.编译和运行异常:SimpleDateFormat 3.处理异常:方法声明抛出 4.finally关键字:catch相当于 ...
- Java线程安全(卖票案例) 如何解决线程安全(synchronized ,显示锁Lock)
文章目录 线程安全 解决方法: 1.同步代码块 2.同步方法 3.显示锁 显示锁与隐式锁的区别 4.公平锁与非公平锁 线程安全 经典问题:卖票问题,多个线程一起执行该任务,当余票只有1一张时,三个线程 ...
最新文章
- thinkphp查询
- 树莓派镜像源切换之旅.md
- 菜鸟学Linux 第007篇笔记 简单命令的使用讲解(文本、时间、目录)
- Android Studio 插件开发详解四:填坑
- alias--linux
- 【MFC系列-第14天】MFC核心类库的成员介绍(记事本快捷键)
- 自学导航页(待续ing)
- hdu 1520 Anniversary party(第一道树形dp)
- 第十篇 requests模块
- c语言编程软件平板_ipad可以编程c语言吗
- 手机12306买卧铺下铺技巧_12306网上购下铺技巧(亲历版)
- java 修改mac地址_XP下修改MAC地址
- Linux设备模型分析之device_driver(基于3.10.1内核)
- 115道Java面试题及答案分享,java程序员赶紧收好
- dell计算机在桌面不显示,你好,在吗?我的戴尔笔记本电脑桌面图标不显示为什么?...
- Android开发之关机广播
- mysql 中间件 atlas_MySQL中间件-Atlas
- CTYZ的树论赛(P5557 旅行/P5558 心上秋/P5559 失昼城的守星使)
- thymeleaf 调用后台方法
- 【太阳黑子预测】太阳黑子变化规律预测matlab仿真