看了这个有趣的例子,你就能秒懂Java中的多线程同步了!
作者:智慧zhuhuix
cnblogs.com/zhuhuix/p/12970326.html
写在前面
把技术概念通过文字的形式写下来,理清逻辑,加深认知;
把知识点通过系列文章的形式分段写下来,让思维进行刻意的训练;
把难懂的东西通过有趣的故事或者例子讲出来,让技术变得生动。
电影票的案例
单线程的例子
我们设定有一个电影院,该电影院开张不久,在入口的旁边只设立了一个售票点A,顾客看电影,需要在售票点排队依次买票,买完票后在入口处检票进入电影院观影。
上面的描述用代码来实现,可以是这样的:
1、首先建立一个电影票的类:主要的属性有票的ID,哪个放映厅,哪一排哪一列,放映的电影名称,放映时间及票价。
/*** 通过卖票程序读懂多线程--电影票的类** @author zhuhuix* @date 2020-05-12*/
public class Ticket {//idprivate int ticketId;//放映厅private String room;//行private Integer row;//列private Integer col;//电影名private String filmName;//价格private BigDecimal price;//放映时间private LocalDateTime datetime;private Ticket(){}public Ticket(int ticketId,String room, Integer row, Integer col, String filmName, BigDecimal price, LocalDateTime datetime) {this.ticketId = ticketId;this.room = room;this.row = row;this.col = col;this.filmName = filmName;this.price = price;this.datetime = datetime;}public int getTicketId() {return ticketId;}public void setTicketId(int ticketId) {this.ticketId = ticketId;}public String getRoom() {return room;}public void setRoom(String room) {this.room = room;}public Integer getRow() {return row;}public void setRow(Integer row) {this.row = row;}public Integer getCol() {return col;}public void setCol(Integer col) {this.col = col;}public String getFilmName() {return filmName;}public void setFilmName(String filmName) {this.filmName = filmName;}public BigDecimal getPrice() {return price;}public void setPrice(BigDecimal price) {this.price = price;}public LocalDateTime getDatetime() {return datetime;}public void setDatetime(LocalDateTime datetime) {this.datetime = datetime;}@Overridepublic String toString() {return "Ticket{" +"ticketId=" + ticketId +", room='" + room + '\'' +", row=" + row +", col=" + col +", filmName='" + filmName + '\'' +", price=" + price +", datetime=" + datetime +'}';}
}
2、其次建立一个顾客的类:主要的属性有票的ID,购买的电影票;成员方法有买票。
/*** 通过卖票程序读懂多线程--顾客类** @author zhuhuix* @date 2020-05-12*/
public class Customer {//顾客idprivate int customerId;//购买的电影票private Ticket ticket;public Customer(int customerId) {this.customerId = customerId;}//顾客买票public void buyTicket(Ticket ticket) {this.ticket = ticket;}public int getCustomerId() {return customerId;}public void setCustomerId(int customerId) {this.customerId = customerId;}public Ticket getTicket() {return ticket;}public void setTicket(Ticket ticket) {this.ticket = ticket;}@Overridepublic String toString() {return "Customer{" +"customerId=" + customerId +", ticket=" + ticket.toString() +'}';}
}
3、最后写一个主程序,生成电影票的列表,设定上门观影的顾客人数,依次买票,输出电影票购买的状态。
/*** 通过卖票程序读懂多线程--单线程程序** @author zhuhuix* @date 2020-05-12*/
public class TicketSingle {private static final String ROOM = "中央放映厅";private static final int ROW = 10;private static final int COL = 20;private static final String FILM_NAME = "战狼3";private static final BigDecimal PRICE = BigDecimal.valueOf(30);private static List<Ticket> tickets = new ArrayList<>();private static final int CUSTOMER_COUNT = 250;private static List<Customer> customers = new ArrayList<>(CUSTOMER_COUNT);public static void main(String[] args) {//中央放映厅总共有250个座位,2020-05-12 18:00 放映战狼3,售票价格为30元int ticketId=1;for (int row = 1; row <= ROW; row++) {for (int col = 1; col <= COL; col++) {Ticket ticket = new Ticket(ticketId++, ROOM, row, col,FILM_NAME, PRICE,LocalDateTime.of(2020, 5, 10, 18, 00));tickets.add(ticket);}}Iterator<Ticket> iterator = tickets.iterator();while (iterator.hasNext()) {System.out.println(iterator.next().toString());}//顾客到售票点进行随机买票Collections.shuffle(tickets);int index = 1;while (tickets.size() > 0 && index <= CUSTOMER_COUNT) {Ticket ticket = tickets.get(tickets.size() - 1);Customer customer = new Customer(index);customer.buyTicket(ticket);customers.add(customer);tickets.remove(ticket);System.out.println(tickets.size() + "," + index);System.out.println(index + "号顾客买到了"+ "第" + customer.getTicket().getRow() + "行,第" + customer.getTicket().getCol() + "列的票");index++;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("电影票出售情况如下:");//剩余票的状态System.out.println("剩余票数:" + tickets.size());Iterator<Ticket> ticketIterator = tickets.iterator();while (ticketIterator.hasNext()) {System.out.println(ticketIterator.next().toString());}//顾客购买情况System.out.println("买到票的人数:" + customers.size());Iterator<Customer> customerIterator = customers.iterator();while (customerIterator.hasNext()) {System.out.println(customerIterator.next().toString());}System.out.println("未买到票的人数:" +(CUSTOMER_COUNT- customers.size()));}
}
主程序的输出情况是这样的:
从单线程转向多线程
一切井然有序,程序也运行得很好,那我们继续往 下看,由于观影顾客人数的增加,电影院对放映厅做了改造:
增加座位;
增设两个卖票窗口。
也就说原来只有一个窗口排队单通道执行的程序变了,要允许原来的售票点与新增的售票点,同时进行卖票了。
有问题的多线程的例子
我们先简单的在单线程的程序上做个多线程的改造:建立一个多线程的类,重写run方法,将顾客买票的过程移至run方法中,在主程中设立”售票点A“,”售票点B“,”售票点C“三个线程让其同时运行,对了,别忘了把ArrayList这个数据结构也改成Vector。改造后的程序是这样的:
/*** 通过卖票程序读懂多线程--多线程** @author zhuhuix* @date 2020-05-12*/
public class TicketThread extends Thread {private static final String ROOM = "中央放映厅";private static final int ROW = 20;private static final int COL = 30;private static final String FILM_NAME = "战狼3";private static final BigDecimal PRICE = BigDecimal.valueOf(30);private static List<Ticket> tickets = new Vector<>();private static final int CUSTOMER_COUNT = 800;private static int customerId = 1;private static List<Customer> customers = new Vector<>(CUSTOMER_COUNT);TicketThread(String name) {super(name);}@Overridepublic void run() {while (tickets.size() > 0 && customerId <= CUSTOMER_COUNT) {Ticket ticket = tickets.get(tickets.size() - 1);ticket.setWindow(Thread.currentThread().getName());Customer customer = new Customer(customerId);customer.buyTicket(ticket);customers.add(customer);tickets.remove(ticket);System.out.println(tickets.size() + "," + customerId);System.out.println(Thread.currentThread().getName() + ":" + customerId + "号顾客买到了"+ "第" + customer.getTicket().getRow() + "行,第" + customer.getTicket().getCol() + "列的票");customerId++;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) throws InterruptedException {//中央放映厅总共有250个座位,2020-05-12 18:00 放映战狼3,售票价格为30元int ticketId = 1;for (int row = 1; row <= ROW; row++) {for (int col = 1; col <= COL; col++) {Ticket ticket = new Ticket(ticketId++, ROOM, row, col,FILM_NAME, PRICE,LocalDateTime.of(2020, 5, 10, 18, 00));tickets.add(ticket);}}Iterator<Ticket> iterator = tickets.iterator();while (iterator.hasNext()) {System.out.println(iterator.next().toString());}//顾客到售票点进行随机买票Collections.shuffle(tickets);TicketThread ticketThreadA = new TicketThread("售票点A");TicketThread ticketThreadB = new TicketThread("售票点B");TicketThread ticketThreadC = new TicketThread("售票点C");ticketThreadA.start();ticketThreadB.start();ticketThreadC.start();ticketThreadA.join();ticketThreadB.join();ticketThreadC.join();System.out.println("电影票出售情况如下:");//剩余票的状态System.out.println("总共票数:" + ROW * COL + ",剩余票数:" + tickets.size());Iterator<Ticket> ticketIterator = tickets.iterator();while (ticketIterator.hasNext()) {System.out.println(ticketIterator.next().toString());}//顾客购买情况System.out.println("买到票的人数:" + customers.size());Iterator<Customer> customerIterator = customers.iterator();while (customerIterator.hasNext()) {System.out.println(customerIterator.next().toString());}System.out.println("未买到票的人数:" + (CUSTOMER_COUNT - customers.size()));}
}
运行一下:总共只有600张票,买到票的人有614人?那进了电影院顾客肯定得投诉。
线程同步问题
我们分析一下:
电影票总共只有600张,三个窗口同时卖这600张票,电影票是个共享的池子,在多线程术语上称为”共享资源“或”临界资源“,每个线程访问这些资源时,要保证”同步“:售票点A要卖第10排第9列的座位时,当且仅当同一时刻只有售票点A才能访问这个座位对应的电影票,也就是所谓的不能一票多卖。
那多线程如何保证同步?通过加锁!! 加锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。(Java知音公众号中回复“面试题聚合”,送你一份面试题宝典!)
保证线程同步的例子
为了可以简单地说明加锁可以保证多线程同步,在下面的例子中仅实现对电影票共享池进行加锁。
/*** 通过卖票程序读懂多线程--多线程** @author zhuhuix* @date 2020-05-12*/
public class TicketThread extends Thread {private static final String ROOM = "中央放映厅";private static final int ROW = 20;private static final int COL = 30;private static final String FILM_NAME = "战狼3";private static final BigDecimal PRICE = BigDecimal.valueOf(30);private volatile static List<Ticket> tickets = new Vector<>();private static final int CUSTOMER_COUNT = 800;private static int customerId = 1;private volatile static List<Customer> customers = new Vector<>(CUSTOMER_COUNT);TicketThread(String name) {super(name);}@Overridepublic void run() {while (tickets.size() > 0 && customerId <= CUSTOMER_COUNT) {synchronized (TicketThread.class) {//线程内两次判断,防止tickets 数组溢出if (tickets.size()>0) {Ticket ticket = tickets.get(tickets.size() - 1);ticket.setWindow(Thread.currentThread().getName());Customer customer = new Customer(customerId);customer.buyTicket(ticket);customers.add(customer);tickets.remove(ticket);System.out.println(tickets.size() + "," + customerId);System.out.println(Thread.currentThread().getName() + ":" + customerId + "号顾客买到了"+ "第" + customer.getTicket().getRow() + "行,第" + customer.getTicket().getCol() + "列的票");customerId++;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}} }}public static void main(String[] args) throws InterruptedException {//中央放映厅总共有250个座位,2020-05-12 18:00 放映战狼3,售票价格为30元int ticketId = 1;for (int row = 1; row <= ROW; row++) {for (int col = 1; col <= COL; col++) {Ticket ticket = new Ticket(ticketId++, ROOM, row, col,FILM_NAME, PRICE,LocalDateTime.of(2020, 5, 10, 18, 00));tickets.add(ticket);}}Iterator<Ticket> iterator = tickets.iterator();while (iterator.hasNext()) {System.out.println(iterator.next().toString());}//顾客到售票点进行随机买票Collections.shuffle(tickets);TicketThread ticketThreadA = new TicketThread("售票点A");TicketThread ticketThreadB = new TicketThread("售票点B");TicketThread ticketThreadC = new TicketThread("售票点C");ticketThreadA.start();ticketThreadB.start();ticketThreadC.start();ticketThreadA.join();ticketThreadB.join();ticketThreadC.join();System.out.println("电影票出售情况如下:");//剩余票的状态System.out.println("总共票数:" + ROW * COL + ",剩余票数:" + tickets.size());Iterator<Ticket> ticketIterator = tickets.iterator();while (ticketIterator.hasNext()) {System.out.println(ticketIterator.next().toString());}//顾客购买情况System.out.println("买到票的人数:" + customers.size());Iterator<Customer> customerIterator = customers.iterator();while (customerIterator.hasNext()) {System.out.println(customerIterator.next().toString());}System.out.println("未买到票的人数:" + (CUSTOMER_COUNT - customers.size()));}
}
运行情况如下:
票不超卖了:
每个窗口也实现了同步并发卖票:
同步的代码主要的改变来自于:
1、将卖票的过程用synchronized修饰,实现锁的互斥,具体可以参考java多线程:
https://blog.csdn.net/jpgzhu/article/details/105967947
synchronized (TicketThread.class) {Ticket ticket = tickets.get(tickets.size() - 1);ticket.setWindow(Thread.currentThread().getName());Customer customer = new Customer(customerId);customer.buyTicket(ticket);customers.add(customer);tickets.remove(ticket);System.out.println(tickets.size() + "," + customerId);System.out.println(Thread.currentThread().getName() + ":" + customerId + "号顾客买到了"+ "第" + customer.getTicket().getRow() + "行,第" + customer.getTicket().getCol() + "列的票");customerId++;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}}
2、将共享资源用volatile 修饰,实现线程访问的可视化,具体可以参考上文链接。
private volatile static List<Ticket> tickets = new Vector<>();
private volatile static List<Customer> customers = new Vector<>(CUSTOMER_COUNT);
写在最后
程序所有的表达,归根到底都是逻辑问题。而逻辑的核心是清晰高效地思考问题。对于多线程的理解,大家一定要起手来写一些例程,融汇贯通才能体会到真谛,才能真正应用到工作实践中去。
END
Java面试题专栏
【81期】面试官:说说HashMap 中的容量与扩容实现
【82期】面试中被问到SQL优化,看这篇就对了!
【83期】面试被问到了Redis和MongoDB的区别?看这里就对了
【84期】面试中设计模式能问些什么?比如说一下三种单例模式实现
【85期】谈谈Java面向对象设计的六大原则,中高级面试常问!
【86期】五个刁钻的String面试问题及解答
【87期】面试官问:Java序列化和反序列化为什么要实现Serializable接口
【88期】面试官问:你能说说 Spring 中,接口的bean是如何注入的吗?
【89期】面试官 5 连问一个 TCP 连接可以发多少个 HTTP 请求?
【90期】面试官:说一下使用 Redis 实现大规模的帖子浏览计数的思路
我知道你 “在看”
看了这个有趣的例子,你就能秒懂Java中的多线程同步了!相关推荐
- 将一个类改成线程_看了这个有趣的例子,相信你就秒懂多线程同步了
电影票的案例 单线程的例子 我们设定有一个电影院,该电影院开张不久,在入口的旁边只设立了一个售票点A,顾客看电影,需要在售票点排队依次买票,买完票后在入口处检票进入电影院观影. 上面的描述用代码来实现 ...
- Java基础——深入理解Java中的多线程(超级详细,值得你看)
Java中的多线程 进程(process)是程序的一次执行过程,或是正在运行的有一个程序,或是正在运行的一个程序.是一个动态的过程:有它自身的产生.存在和消亡的过程.--生命周期. 线程(thread ...
- Java 中的多线程你只要看这一篇就够了
作者丨纳达丶无忌 https://www.jianshu.com/p/40d4c7aebd66 引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多 ...
- Java中的多线程你只要看这一篇就够了
如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
- java 书 例子_刚学了java中的方法,看了书本的例子不会做,求大神做出来研究下...
深时光 package three.one;public class Cube {/* Cube类定义要求:(1)长length,宽width为double类型,高height 为int类型.(2) ...
- java中的多线程来看一看基础了
一 引言 : 进程是一个正在执行中的程序,每个进程执行都有一个执行顺序,或执行路径: 线程是进程中的内容,是进程中独立的控制单元: 线程控制着进程的执行,或者是一个控制单元: 一个进程中至少有一个线 ...
- 完成这个例子,说出java中针对异常的处理机制。
有一个类为ClassA,有一个类为ClassB,在ClassB中有一个方法b,此方法抛出异常,在ClassA类中有一个方法a,请在这个方法中调用b,然后抛出异常.在客户端有一个类为TestC,有一个方 ...
- 概率论在实际生活的例子_概率论中几个有趣的例子
转载]概率论中几个有趣的例子 [ 2007-6-3 13:06:00 | By: Byron ] 推荐 作者: ni1985 (妮子||从东方席地卷来一团野火), 原发新水木Mathematics 已 ...
- 贝叶斯推理:一个更有趣的例子
贝叶斯推理:一个更有趣的例子 系统中的某用户每天收发的文本信息的数目,都有记录.图-1画出了这些数据.你想了解该用户收发信息的习惯在此期间的变化,是逐渐的还是突然的.你将如何解决问题? %matplo ...
最新文章
- python批量改动指定文件夹文件名称
- Linear Algebra lecture6 note
- JMetro“ Metro”选项卡,Java的TreeView和ContextMenu(JavaFX)
- Integer 与 int 的区别
- Centos7 中查找文件、目录、内容
- python3 介绍
- ES6入门之let、cont
- 师妹:3D视觉方向的招聘,有哪个比较好的社区呢?
- python下载豆丁文档_doc_downloader
- Python爬虫实战-小说网站爬虫开发
- MySQL之表的约束(主键、外键、唯一键、自增长、列描述、默认值、空属性)
- 怎样删除手机自带软件?
- java生成假数据工具类-基于Faker1.0.2
- 【入门】算法初步1 排序
- CenterPoint 学习笔记
- 【软件测试】:“用户登录”功能测试用例设计方法
- SC8701 120W DC TO DC 电源模块的设计
- java接口方式调用海康大华摄像机预览。
- 光电容积脉搏波描记法PPG
- 批处理之ren命令-可批量修改文件名