作者:智慧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中的多线程同步了!相关推荐

  1. 将一个类改成线程_看了这个有趣的例子,相信你就秒懂多线程同步了

    电影票的案例 单线程的例子 我们设定有一个电影院,该电影院开张不久,在入口的旁边只设立了一个售票点A,顾客看电影,需要在售票点排队依次买票,买完票后在入口处检票进入电影院观影. 上面的描述用代码来实现 ...

  2. Java基础——深入理解Java中的多线程(超级详细,值得你看)

    Java中的多线程 进程(process)是程序的一次执行过程,或是正在运行的有一个程序,或是正在运行的一个程序.是一个动态的过程:有它自身的产生.存在和消亡的过程.--生命周期. 线程(thread ...

  3. Java 中的多线程你只要看这一篇就够了

    作者丨纳达丶无忌 https://www.jianshu.com/p/40d4c7aebd66 引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多 ...

  4. Java中的多线程你只要看这一篇就够了

    如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...

  5. java 书 例子_刚学了java中的方法,看了书本的例子不会做,求大神做出来研究下...

    深时光 package three.one;public class Cube {/*  Cube类定义要求:(1)长length,宽width为double类型,高height 为int类型.(2) ...

  6. java中的多线程来看一看基础了

    一  引言 : 进程是一个正在执行中的程序,每个进程执行都有一个执行顺序,或执行路径: 线程是进程中的内容,是进程中独立的控制单元: 线程控制着进程的执行,或者是一个控制单元: 一个进程中至少有一个线 ...

  7. 完成这个例子,说出java中针对异常的处理机制。

    有一个类为ClassA,有一个类为ClassB,在ClassB中有一个方法b,此方法抛出异常,在ClassA类中有一个方法a,请在这个方法中调用b,然后抛出异常.在客户端有一个类为TestC,有一个方 ...

  8. 概率论在实际生活的例子_概率论中几个有趣的例子

    转载]概率论中几个有趣的例子 [ 2007-6-3 13:06:00 | By: Byron ] 推荐 作者: ni1985 (妮子||从东方席地卷来一团野火), 原发新水木Mathematics 已 ...

  9. 贝叶斯推理:一个更有趣的例子

    贝叶斯推理:一个更有趣的例子 系统中的某用户每天收发的文本信息的数目,都有记录.图-1画出了这些数据.你想了解该用户收发信息的习惯在此期间的变化,是逐渐的还是突然的.你将如何解决问题? %matplo ...

最新文章

  1. python批量改动指定文件夹文件名称
  2. Linear Algebra lecture6 note
  3. JMetro“ Metro”选项卡,Java的TreeView和ContextMenu(JavaFX)
  4. Integer 与 int 的区别
  5. Centos7 中查找文件、目录、内容
  6. python3 介绍
  7. ES6入门之let、cont
  8. 师妹:3D视觉方向的招聘,有哪个比较好的社区呢?
  9. python下载豆丁文档_doc_downloader
  10. Python爬虫实战-小说网站爬虫开发
  11. MySQL之表的约束(主键、外键、唯一键、自增长、列描述、默认值、空属性)
  12. 怎样删除手机自带软件?
  13. java生成假数据工具类-基于Faker1.0.2
  14. 【入门】算法初步1 排序
  15. CenterPoint 学习笔记
  16. 【软件测试】:“用户登录”功能测试用例设计方法
  17. SC8701 120W DC TO DC 电源模块的设计
  18. java接口方式调用海康大华摄像机预览。
  19. 光电容积脉搏波描记法PPG
  20. 批处理之ren命令-可批量修改文件名

热门文章

  1. 苹果挖迪士尼墙脚:喜获视频服务总监
  2. OPPO首部5G手机亮相 10倍混合光学变焦技术惊艳MWC
  3. 女儿是程序员爸爸的小棉袄,礼物太暖心
  4. 不用 Python 自带的 Dict 实现自己的 HashTable
  5. leetcode--single number.
  6. OpenCV系列(一)之图像平滑
  7. 华三交换机配置多个镜像口_H3C交换机端口镜像配置的方法
  8. libjpeg在windows下的编译
  9. Thymeleaf的入门(一)
  10. 【Elasticsearch】Elasticsearch查询参数batched_reduce_size的解释