用Java 的锁机制实现多线程售票案例

news2024/11/28 16:53:35

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注"慕课网"及“慕课网公众号”!

作者:王军伟Tech | 慕课网讲师


1. 前言

本文内容主要是使用 Java 的锁机制对多线程售票案例进行实现。售票案例多数情况下主要关注多线程如何安全的减少库存,也就是剩余的票数,当票数为 0 时,停止减少库存。

本文内容除了关注车票库存的减少,还会涉及到退票窗口,能够更加贴切的模拟真实的场景。

本文需要学习者关注如下两个重点:

  • 掌握多线程的售票机制模型,在后续的工作中如果涉及到类似的场景,能够第一时间了解场景的整体结构;
  • 使用 Condition 和 Lock 实现售票机制。

2. 售票机制模型

售票机制模型是源于现实生活中的售票场景,从开始的单窗口售票到多窗口售票,从开始的人工统计票数到后续的系统智能在线售票。多并发编程能够实现这一售票场景,多窗口售票情况下保证线程的安全性和票数的正确性。

 

如上图所示,有两个售票窗口进行售票,有一个窗口处理退票,这既是现实生活中一个简单的售票机制。

3. 售票机制实现

场景设计

  • 创建一个工厂类 TicketCenter,该类包含两个方法,saleRollback 退票方法和 sale 售票方法;
  • 定义一个车票总数等于 10 ,为了方便观察结果,设置为 10。学习者也可自行选择数量;
  • 对于 saleRollback 方法,当发生退票时,通知售票窗口继续售卖车票;
  • 对 saleRollback 进行特别设置,每隔 5000 毫秒退回一张车票;
  • 对于 sale 方法,只要有车票就进行售卖。为了更便于观察结果,每卖出一张车票,sleep 2000 毫秒;
  • 创建一个测试类,main 函数中创建 2 个售票窗口和 1 个退票窗口,运行程序进行结果观察。
  • 修改 saleRollback 退票时间,每隔 25 秒退回一张车票;
  • 再次运行程序并观察结果。

实现要求:本实验要求使用 ReentrantLock 与 Condition 接口实现同步机制。

实例

public class DemoTest {
        public static void main(String[] args) {
            TicketCenter ticketCenter = new TicketCenter();
            new Thread(new saleRollback(ticketCenter),"退票窗口"). start();
            new Thread(new Consumer(ticketCenter),"1号售票窗口"). start();
            new Thread(new Consumer(ticketCenter),"2号售票窗口"). start();
        }
}

class TicketCenter {
    private int capacity = 10; // 根据需求:定义10涨车票
    private Lock lock = new ReentrantLock(false);
    private Condition saleLock = lock.newCondition();
    // 根据需求:saleRollback 方法创建,为退票使用
    public void saleRollback() {
        try {
            lock.lock();
            capacity++;
            System.out.println("线程("+Thread.currentThread().getName() + ")发生退票。" + "当前剩余票数"+capacity+"个");
            saleLock.signalAll(); //发生退票,通知售票窗口进行售票
        } finally {
            lock.unlock();
        }
    }

    // 根据需求:sale 方法创建
    public void sale() {
        try {
            lock.lock();
            while (capacity==0) { //没有票的情况下,停止售票
                try {
                    System.out.println("警告:线程("+Thread.currentThread().getName() + ")准备售票,但当前没有剩余车票");
                    saleLock.await(); //剩余票数为 0 ,无法售卖,进入 wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            capacity-- ; //如果有票,则售卖 -1
            System.out.println("线程("+Thread.currentThread().getName() + ")售出一张票。" + "当前剩余票数"+capacity+"个");
        } finally {
            lock.unlock();
        }
    }
}

class saleRollback implements Runnable {
    private TicketCenter TicketCenter; //关联工厂类,调用 saleRollback 方法
    public saleRollback(TicketCenter TicketCenter) {
        this.TicketCenter = TicketCenter;
    }
    public void run() {
        while (true) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            TicketCenter.saleRollback(); //根据需求 ,调用 TicketCenter 的 saleRollback 方法

        }
    }
}
class Consumer implements Runnable {
    private TicketCenter TicketCenter;
    public Consumer(TicketCenter TicketCenter) {
        this.TicketCenter = TicketCenter;
    }
    public void run() {
        while (true) {
            TicketCenter.sale(); //调用sale 方法
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果验证

线程(1号售票窗口)售出一张票。当前剩余票数9个
线程(2号售票窗口)售出一张票。当前剩余票数8个
线程(2号售票窗口)售出一张票。当前剩余票数7个
线程(1号售票窗口)售出一张票。当前剩余票数6个
线程(1号售票窗口)售出一张票。当前剩余票数5个
线程(2号售票窗口)售出一张票。当前剩余票数4个
线程(退票窗口)发生退票。当前剩余票数5个
线程(1号售票窗口)售出一张票。当前剩余票数4个
线程(2号售票窗口)售出一张票。当前剩余票数3个
线程(2号售票窗口)售出一张票。当前剩余票数2个
线程(1号售票窗口)售出一张票。当前剩余票数1个
线程(退票窗口)发生退票。当前剩余票数2个
线程(1号售票窗口)售出一张票。当前剩余票数1个
线程(2号售票窗口)售出一张票。当前剩余票数0个
警告:线程(1号售票窗口)准备售票,但当前没有剩余车票
警告:线程(2号售票窗口)准备售票,但当前没有剩余车票
线程(退票窗口)发生退票。当前剩余票数1个
线程(1号售票窗口)售出一张票。当前剩余票数0个
警告:线程(2号售票窗口)准备售票,但当前没有剩余车票
警告:线程(1号售票窗口)准备售票,但当前没有剩余车票

结果分析:从结果来看,我们正确的完成了售票和退票的机制,并且使用了 ReentrantLock 与 Condition 接口。

代码片段分析 1:看售票方法代码。

public void sale() {
        try {
            lock.lock();
            while (capacity==0) { //没有票的情况下,停止售票
                try {
                    System.out.println("警告:线程("+Thread.currentThread().getName() + ")准备售票,但当前没有剩余车票");
                    saleLock.await(); //剩余票数为 0 ,无法售卖,进入 wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            capacity-- ; //如果有票,则售卖 -1
            System.out.println("线程("+Thread.currentThread().getName() + ")售出一张票。" + "当前剩余票数"+capacity+"个");
        } finally {
            lock.unlock();
        }
    }

主要来看方法中仅仅使用了 await 方法,因为退票是场景触发的,售票窗口无需唤醒退票窗口,因为真实的场景下,可能没有退票的发生,所以无需唤醒。这与生产者与消费者模式存在着比较明显的区别。

代码片段分析 2:看退票方法代码。

public void saleRollback() {
        try {
            lock.lock();
            capacity++;
            System.out.println("线程("+Thread.currentThread().getName() + ")发生退票。" + "当前剩余票数"+capacity+"个");
            saleLock.signalAll(); //发生退票,通知售票窗口进行售票
        } finally {
            lock.unlock();
        }
    }

退票方法只有 signalAll 方法,通知售票窗口进行售票,无需调用 await 方法,因为只要有退票的发生,就能够继续售票,没有库存上限的定义,这也是与生产者与消费者模式的一个主要区别。

总结:售票机制与生产者 - 消费者模式存在着细微的区别,需要学习者通过代码的实现慢慢体会。由于售票方法只需要进入 await 状态,退票方法需要唤醒售票的 await 状态,因此只需要创建一个售票窗口的 Condition 对象。

4. 小结

本文内容主要对售票机制模型进行了讲解,核心内容为售票机制的实现。实现的过程使用 ReentrantLock 与 Condition 接口实现同步机制,也是本节课程的重点知识。


欢迎关注「慕课网」官方帐号,我们会一直坚持提供IT圈优质内容,分享干货知识,大家一起共同成长吧!

本文原创发布于慕课网 ,转载请注明出处,谢谢合作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/572766.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

logback高级特性使用

一、业务需求 日志级别的分类 日志的级别分为: trace:微量,少许的意思,级别最低info:普通的打印信息debug:需要调试时候的关键信息打印warn:警告,不影响使⽤,但需要注…

windows系统python3.7版本pyspider安装

环境:很多的python版本都尝试过安装pyspider,网上多数让python3.6安装,说是这个环境是最佳的环境,测试安装最方便快捷,但是一直报result_worker starting…!!!,卡死在界面…

软件测试技术才是王道,43岁照样拿到年薪70W+,太强了...

最近挺丧的, 可能是之前弦绷的有点紧,现在有点受不了了。 所以突然就泄了气,每天忙完工作的事后就躺在家里打游戏。其实感觉每年都有一段时间是这样丧的。所以我自己其实并不是特别努力的类型,我没办法一直绷着弦的去卷&#xff0…

0基础想入门互联网选择什么好?

互联网岗位划分 研发:技术岗,需要有相关的专业知识。 测试:技术岗,通过相关的程序查找产品中相应的bug。 设计:需要美术素养。 产品经理:设计制定产品的原型,制定每个功能的需求以及输出相应…

论文解读 | IROS 2022:MV6D:在RGB-D图像上使用深度逐点投票网络进行多视角6D姿态估计

原创 | 文 BFT机器人 01 研究背景 在计算机视觉领域,6D姿态估计是一种重要的任务,用于确定物体在3D空间中的位置和方向。它在许多应用领域具有广泛的应用,如机器人操作、虚拟现实、增强现实、物体跟踪等。 然而,传统的6D姿态估计方…

Jmeter实现分布式并发

Jmeter实现分布式并发,即使用远程机执行用例。 环境: VMware Fusion Windows系统是win7。 操作过程 1、Master在jmeter.properties添加remote_hosts 2、Slave在jmeter.properties添加server_port 同时把remote_hosts修改为和主机(Master…

超100篇! VAD论文梳理汇总!

GitHub的一位博主整理了上百篇语音活动检测(VAD)的论文,按照其中使用的特征方法以及适用的环境进行了分类整理,时间跨度为从198*年至2019年。此外,还提供了几个VAD代码,它们的性能表现较好。需要的同学可以…

我的创作纪念日---[需要更开阔的视野!]

文章目录 头绪收获日常 憧憬英语人工智能 希望 头绪 工作很长时间之后,才发现知识的根本,还是在于积累。俗话说好记性不如烂笔头。不管是特定产品相关的知识还是系统类的知识,又或者是语言类的知识,都有很多知识点需要积累。有了…

不会数据分析?无从下手?一文帮你打开数据分析思路

掌握了很多数据分析工具和技能,却依然做不好数据分析。 面对具体的业务问题,我们还是容易两眼一抹黑?除了数据和专业之外,还需要一定的方法论支撑。 文章有点长(误区解释方法论分享)但干货满满&#xff0c…

药用辅料数据查询网站系统-药品辅料数据

药用辅料是指在制药过程中,用于增加药品稳定性、改善口感、提高吸收率等功效的辅助材料。药用辅料的种类繁多,不同的药品需要使用不同的辅料,因此对于药企来说,了解并选用适合自己的药用辅料显得尤为重要。本文将介绍如何利用药用…

jvm之对象大小分析

写在前面 本文看下计算对象大小相关内容。 1:基础内容 1.1:对象的结构 一个对象由对象头和对象体组成,其中对象头包含如下内容: 标记字(mark word):存放GC年龄信息,对象锁信息等…

Hightopo 使用心得(1)- 基本概念

Hightopo 公司 3D 可视化产品有对应的官方手册。但是这些手册内容比较多。对于想学习的新同学来说可能相对比较繁琐。这里本人根据个人使用经验做了一些总结。希望对读者有所帮助。 官方手册地址:Structure (hightopo.com) 本文会提到一些前端开发的概念&#xff…

pdf怎么压缩得小一点?软件压缩更高效

PDF可以在不同操作系统和设备上实现高保真的排版和格式化。然而,随着文档的不断增多和文件大小的增加,传输和存储PDF文件也变得越来越困难。为了解决这个问题,可以使用PDF压缩技术来减小文件大小,提高传输效率。本文将介绍PDF压缩…

pdf转jpg怎么转?转换软件分享

随着数字化时代的到来,我们处理和共享信息的方式也在不断进步。在许多情况下,我们需要将PDF文档转换为图像格式,以便更方便地在网站、社交媒体或其他数字平台上与他人共享。本文将介绍如何将PDF文件转换为JPG图像格式。 有许多在线工具和软件…

1929-2022年全球站点的逐月平均气温数据

气象数据是在各项研究中都经常使用的数据,气象指标包括气温、风速、降水、湿度等指标,其中又以气温指标最为常用!说到气温数据,最详细的气温数据是具体到气象监测站点的气温数据!本次我们为大家带来的就是具体到气象监…

制船业智慧转型,3D轻量化工具赋能数字化!

随着科技的不断进步,计算机辅助设计(CAD)和三维建筑模型技术在造船业中扮演着重要角色。造船业是一个复杂而庞大的行业,涉及到船舶设计、建造模型、制造和施工等多个环节。 为了提高效率、降低成本并保证质量,传统的手…

记录为什么程序跑着跑着突然重启

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

3. 自然语言处理NLP:具体用途(近义词类比词;情感分类;机器翻译)

一、求近义词和类比词 1. 近义词 方法一:在嵌入模型后,可以根据两个词向量的余弦相似度表示词与词之间在语义上的相似度。 方法二:KNN(K近邻) 2. 类比词 使用预训练词向量求词与词之间的类比关系。eg:man&a…

同等学力申硕在职研究生,到底有没有含金量

🔎 同等学力申硕的含金量怎么样?值得报考吗? 所谓同等学力申硕的含金量,其实就是指硕士学位证书所能带来的价值。 同等学力申硕不属于学历教育,硕士学位证书不能提高学历,也就是说我们毕业之后&#xff0…

如何在 GNU Linux 上通过 Nvm 安装 Node 和 Npm?

Node.js 是一个流行的 JavaScript 运行时环境,用于开发服务器端和网络应用程序。它带有一个强大的软件包管理器 npm,可以方便地安装和管理 JavaScript 包和依赖项。在 GNU/Linux 系统上,使用 Nvm(Node Version Manager&#xff09…