文章首发于 2020-11-29

知乎文章:Java线程同步-模拟买票

作者:落雨湿红尘(也是我o)

01 导语

本文使用JAVA代码模拟买票场景下的业务交互,通过示例讲解线程的初始化、线程同步等java线程基础知识。

提出这样一个问题:三个人排队买电影票(每张5元)。三个人按前后顺序分别为张某(拿着20元钞票)、李某(拿着10元钞票)和赵某(拿着5元钞票)。而售票员的钱箱中现在只有3张5元钞票,试对这样一个场景进行模拟

02 线程同步问题

当多个线程对共享资源进行操作时,势必会出现进程间相互争夺资源的情况,而线程同步问题则是规划各线程间操作顺序,让每个线程不受其他线程打扰的情况下独自安全的操作共享资源。

而在本场景中,那个售票员就是这样一个共享资源。在对每个人受理业务(即每个线程)的过程中,这个售票员钱箱里的钱会发生变化,但是每个人受理业务时,不能被其他线程打扰(即别人不能插队)

下面构造售票员这样一个共享资源,其有一个找零功能change()。

找零算法的思想是:对于需要找零的金额moneyChange,若其等于票价,就不需要找零。否则,用售票元手上的每个币种,从大到小依次找零。每找零一次,找零金额moneyChange就会减小。最后当最小币种找零时,若其不足以找零,说明售票员手上的钱是不足以找零的,返回一个false。若其可以找零,说明售票员手上的钱足以找零,把受理对象钱收下,给他票,返回true。

//售票员
class Seller { public static final int PRICE = 5;//票单价5元private int five ;//5元纸币数量private int ten ;//10元纸币数量private int twenty ;//20元纸币数量//getter and setter   public int getFive() {return five;}public void setFive(int five) {this.five = five;}public int getTen() {return ten;}public void setTen(int ten) {this.ten = ten;}public int getTwenty() {return twenty;}public void setTwenty(int twenty) {this.twenty = twenty;}public Seller(int five, int ten, int twenty) {this.five = five;this.ten = ten;this.twenty = twenty;}/***找零* @param target受理对象* @param five 收到的5元纸币数* @param ten 收到的10元纸币数* @param twenty 收到的20元纸币数*/public  boolean change(String target,int five,int ten ,int twenty){int pay = five*5+ten*10 +twenty*20;//支付的钱int moneyChange = pay - PRICE;//找零if(moneyChange == 0){//不需要找0System.out.println("售票员收到"+pay+"元钱,递给"+target+"1张入场券\n");this.setFive(this.five + five);return true;}//使用不同币种依次找零//使用20元纸币找零int twentyChange = moneyChange/20;if (this.twenty >= twentyChange) {moneyChange =  moneyChange -twentyChange*20;this.setTwenty(this.twenty - twentyChange);}else {moneyChange = moneyChange-this.twenty*20;twentyChange = this.twenty;//钱箱中只能找这么多20块的this.setTwenty(0);//全部拿去找零}//使用10元纸币找零int tenChange = moneyChange/10;if (this.ten >= tenChange) {moneyChange =  moneyChange -tenChange*10;this.setTen(this.ten - tenChange);}else {moneyChange = moneyChange-this.ten*10;tenChange = this.ten;//只能找这么多10块的this.setTen(0);//全部拿去找零}//使用5元纸币找零int fiveChange = moneyChange/5;if (this.five >= fiveChange) {moneyChange =  moneyChange -fiveChange*5;this.setFive(this.five - fiveChange);//此时可以说明钱箱中的钱足以找零System.out.println("售票员收到"+pay+"元钱,递给"+target+"1张入场券,并找零20*"+twentyChange+"+10*"+tenChange+"+5*"+fiveChange+"元\n");//收钱this.setFive(this.five + five);this.setTen(this.ten +ten);this.setTwenty(this.twenty +twenty);return true;         }else {//此时,钱箱中的所有币种加起来都不足以找零return false;}                      }
}

03 线程初始化

Java中,线程初始化一般有两种方式:

1.继承Thread类,重写run方法

运行线程时,直接使用实例化对象的start方法即可运行线程,原因是Thread类及其子类带有start方法。以下为伪代码

//买票过程
class BuyAndSell extends Thread {//对线程各参数的设定//线程构造方法@Overridepublic void run() {//你要让线程执行的功能,在这里书写          }   }
}
public class Main {public static void main(String args[ ]) {//线程构造,这里使用了向上转型Thread zhang = new BuyAndSell();//运行线程zhang.start();}}

2.实现Runable接口,重写run方法

此时BuyAndSell是一个实现了Runnable接口的实现类,需要将该实现类的对象如下面的zhang,传给Thread类,让Thread类构造线程并调用start方法启动线程。以下为伪代码

class BuyAndSell implements Runnable {//设定线程各参数//线程构造方法等@Overridepublic void run() {//你要让线程执行的功能,在这里书写}  }
}
public class Main {public static void main(String args[ ]) {//线程构造,使用接口向上转型Runnable zhang = new BuyAndSell();//运行线程new Thread(zhang).start();}}

两种方法都可以构造线程,但是实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,是最优之选。因此,我也使用此方式对线程进行构造

//买票过程
class BuyAndSell implements Runnable {private Seller mb;private String name;//买票人名字private int five; private int ten ;private int twenty;public BuyAndSell(Seller mb, String name, int five, int ten, int twenty) {this.mb = mb;this.name = name;this.five = five;this.ten = ten;this.twenty = twenty;}@Overridepublic void run() {// 对公共资源售票员加锁,每当有一个线程办理业务时,不能被其他线程打扰synchronized (mb) {int pay = five*5+ten*10 +twenty*20;//支付的钱System.out.println(name+"拿出"+pay+"元钱买票");
//          try {
//              Thread.sleep(100);
//          } catch (InterruptedException e1) {
//              // TODO Auto-generated catch block
//              e1.printStackTrace();
//          }boolean flag = mb.change(name,five, ten, twenty);while(!flag){System.out.println("钱箱里的钱不足以找零,"+name+"请稍等..\n");try {mb.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(name+"重新拿出"+pay+"元钱买票");flag = mb.change(name,five, ten, twenty);}
//  此时说明办理成功,有零钱了,唤醒其他线程来争夺资源。即叫没有买到票的人来继续买票mb.notifyAll();                       }       }
}

在run中,使用静态代码块对公共资源售票员加锁,每当有一个受理人办理业务时,不能被其他线程打扰(即该线程拿到了Seller对象mb的锁)。如果钱不够找零(即flag=false)就让这个受理人稍等,即让这个线程wait()(此时,调用wait的同时,也把他原来拿到的锁给释放了)。

有朋友想要继承Thread实现的,我也放这里啦

class BuyAndSell1 extends Thread {private Seller mb;private String name;//买票人名字private int five; private int ten ;private int twenty;public BuyAndSell1(Seller mb, String name, int five, int ten, int twenty) {this.mb = mb;this.name = name;this.five = five;this.ten = ten;this.twenty = twenty;}@Overridepublic void run() {// TODO Auto-generated method stubsynchronized (mb) {int pay = five*5+ten*10 +twenty*20;//支付的钱System.out.println(name+"拿出"+pay+"元钱买票");
//          try {
//              Thread.sleep(100);
//          } catch (InterruptedException e1) {
//              // TODO Auto-generated catch block
//              e1.printStackTrace();
//          }boolean flag = mb.change(name,five, ten, twenty);while(!flag){System.out.println("钱箱里的钱不足以找零,"+name+"请稍等..\n");try {mb.wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(name+"重新拿出"+pay+"元钱买票");flag = mb.change(name,five, ten, twenty);}
//              System.out.println("售票员钱箱里有20元纸币"+mb.getTwenty()+"张,10元纸币"+mb.getTen()+"张,5元纸币"+mb.getFive()+"张\n");mb.notifyAll();}}}

04 业务模拟

主函数中,开始整个业务流程

public class Main {public static void main(String args[ ]) {Seller seller = new Seller(3,0,0);//初始只有3张5元纸币System.out.println("售票员钱箱里有20元纸币"+seller.getTwenty()+"张,10元纸币"+seller.getTen()+"张,5元纸币"+seller.getFive()+"张\n");//构造3个买票过程//Runable 接口实现//张某(拿着20元钞票Runnable zhang = new BuyAndSell(seller,"张某", 0, 0, 1);//李某(拿着10元钞票Runnable li = new BuyAndSell(seller,"李某", 0, 1, 0);//赵某(拿着5元钞票Runnable zhao = new BuyAndSell(seller,"赵某", 1, 0, 0);new Thread(zhang).start();new Thread(li).start();new Thread(zhao).start();/**Thread继承实现//张某(拿着20元钞票Thread zhang = new BuyAndSell1(seller,"张某", 0, 0, 1);//李某(拿着10元钞票Thread li = new BuyAndSell1(seller,"李某", 0, 1, 0);//赵某(拿着5元钞票Thread zhao = new BuyAndSell1(seller,"赵某", 1, 0, 0);zhang.start();li.start();zhao.start();*/}
}

运行结果会随机不同,大概是因为线程会随着cpu时间片的分配随机执行

运行结果1

运行结果2

以上代码,经过本人调试,没有看出问题来。如有问题,多半是编辑错误,欢迎指正!

Java线程同步-模拟买票相关推荐

  1. Java线程模仿电影院买票

    模拟唐僧师徒4人买电影票.电影票10元一张,唐僧拿着一张50元的.孙悟空那和 猪八戒都拿着一张20的. 沙僧拿着一张10元的.此时售票员手中只有一个10元的. 模拟他们4个人买票. package c ...

  2. 关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)

    在多线程应用程序中经常会遇到线程同步的问题.比如:两个线程A.线程B可能会 "同时" 执行同一段代码,或修改同一个变量.而很多时候我们是不希望这样的. 这时候,就需要用到线程同步. ...

  3. java线程同步——竞争条件的荔枝+锁对象

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--竞争条件的荔枝+锁对象 的相关知识: 0.2) for full sou ...

  4. java线程 同步与异步 线程池

    1)多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线 程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有时为了解 决此问题,优先考虑 ...

  5. (转) Java线程同步阻塞, sleep(), suspend(), resume(), yield(), wait(), notify()

    为了解决对共享存储区的访问冲突,Java 引入了同步机制.但显然不够,因为在任意时刻所要求的资源不一定已经准备好了被访问,反过来,同一时刻准备好了的资源也可能不止一个. 为解决访问控制问题,Java ...

  6. java线程同步——条件对象+synchronized 关键字

    [0]README 0.1) 本文描述转自 core java volume 1, 源代码为原创,旨在理解 java线程同步--条件对象+synchronized 关键字 的相关知识: 0.2)for ...

  7. Java --- 线程同步和异步的区别

    1. Java 线程 同步与异步 多线程并发时,多个线程同时请求同一个资源,必然导致此资源的数据不安全,A线程修改了B线程的处理的数据,而B线程又修改了A线程处理的数理.显然这是由于全局资源造成的,有 ...

  8. Java线程同步的几种方式

    Java线程同步的几种方式 1.使用synchronized关键字  它的工作是对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性.  synchronized关键字的用法: ...

  9. java线程同步原理

    一. java线程同步原理 java会为每个object对象分配一个monitor,当某个对象的同步方法(synchronized methods)被多个线程调用时,该对象的monitor将负责处理这 ...

最新文章

  1. VirtualBox中修改Ubuntu-server分辨率
  2. “读书”频道的一些链接错误
  3. 解决Win7英文版显示中文乱码
  4. J2EE后台UI系统框架搭建-EXTJs使用(4.1 GPL版本)
  5. 如何让Excel里显示的数字避免通过科学计数法来显示
  6. 基于JAVA+SpringMVC+Mybatis+MYSQL的医院管理系统
  7. InnoDB与Myisam的六大区别
  8. python打包生成so文件
  9. JS实现复制到剪贴板功能
  10. html中qq咨询的代码,QQ样式的在线客服代码
  11. 做项目管理需要哪些技能?
  12. Ubantu 安装 Oracle JDK
  13. 《编码隐藏在计算机软硬件背后的语言》读感
  14. 如何利用等比频宽公式将信号分解成部分重叠的子频段
  15. 生动的ajax图片显示效果,LightBox
  16. linux基础命令学习笔记
  17. Hive错误之 Execution Error, return code 2 from org.apache.hadoop.hive.ql.exec.mr.MapRedTask错误分析
  18. 我的世界(12)-服务器领地(Residence插件)
  19. 云服务器可以修改ip,云服务器的ip可以更换吗
  20. DTS、杜比2.0、杜比5.1、AC3

热门文章

  1. keil C语言编程 位地址定义,Keil C编写下位机程序的小技巧和注意点
  2. 2022细胞生物学实验原理复习资料汇总
  3. 画中画 视频叠加 视频覆盖 overlay
  4. Excel:某一列前面统一加上相同的内容
  5. 前端白屏问题_H5白屏问题
  6. [java]轻量级隔离化的excel导入导出的导入导出,解放导入导出冗余代码(有github源码)
  7. 高速PCB基础——电阻,电容,电感(第三讲)
  8. Echarts真正态分布图
  9. JavaSE学习笔记
  10. 360极速浏览器2013 v7.5.3.202 官方版