首页 > 编程语言 >使用Java 锁机制实现多线程售票案例

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

时间:2023-05-26 11:33:13浏览次数:43  
标签:售票 TicketCenter Java 退票 线程 窗口 多线程 票数

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

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


1.前言

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

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

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

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


2. 售票机制模型

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

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

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


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圈优质内容,分享干货知识,大家一起共同成长吧!

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

标签:售票,TicketCenter,Java,退票,线程,窗口,多线程,票数
From: https://blog.51cto.com/u_15771948/6354632

相关文章

  • JAVA语言开发springboot框架实现的自动化立体智慧仓库WMS
    技术架构技术框架:SpringBoot+layui+HTML+CSS+JS运行环境:jdk8+IntelliJIDEA+maven3+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个jar文件。宝塔新建一个数据库,导入数据库文件,数据......
  • 【Java基础】万字长文深入理解Java反射机制
    大家好,我是程序员青戈,一个被Bug耽误了才艺的程序员......
  • 用费曼学习法教小姐姐学习Java面向对象
    大家好,我是程序员青戈,一个被Bug耽误了才艺的程序员......
  • java程序自动获取IP地址
    /***获取IP地址的方法*@paramrequest传一个request对象下来*@return*/publicstaticStringgetIpAddress(HttpServletRequestrequest){Stringip=request.getHeader("x-forwarded-for");if(ip==null||ip.length(......
  • 从删库到跑路,老大爷学Java的心酸历程
    大家好,我是程序员青戈,一个被Bug耽误了才艺的程序员......
  • Java宝塔部署基于SSM超市订单系统
    技术架构技术框架:Spring+SpringMVC+Hibernate+mysql5.7运行环境:jdk8+IntelliJIDEA+maven+宝塔面板宝塔部署教程回到IDEA,点击编辑器右侧maven图标,执行package,完成后就会在根目录里生成一个target目录,在里面会打包出一个war文件。宝塔新建一个数据库,导入数据库文件......
  • 复习JavaDay07
    线程的5种状态新生状态:Threadthread=newThread();就绪状态:当调用start()方法,线程立即进入就绪状态,但并不以为着立即调度执行运行状态:进入运行状态,线程才真正执行线程体的代码块。阻塞状态:当调用sleep(),wait或者同步锁时,线程进入阻塞状态,就是代码不往下执行阻塞事件解......
  • 使用resource读取properties文件,出现Cause: java.sql.SQLException: No suitable driv
    ###Errorqueryingdatabase.Cause:java.sql.SQLException:Nosuitabledriverfoundforhttp://maven.apache.org###Theerrormayexistincom/louis/dao/UserMapper.xml###Theerrormayinvolvecom.louis.dao.UserMapper.getUserList###Theerroroccurred......
  • JavaScript 格式化金额
    JavaScript格式化金额一、使用toLocaleString()要格式化金额,可以使用JavaScript的toLocaleString()方法。该方法可以将数字转换为本地化的字符串表示形式,并可以指定货币符号、小数点和千位分隔符等格式。代码如下:美元constamount=1234567.89;constformattedAmou......
  • Java设计模式-策略模式
    简介在软件开发中,设计模式是为了解决常见问题而提供的一套可重用的解决方案。策略模式(StrategyPattern)是其中一种常见的设计模式,它属于行为型模式。该模式的核心思想是将不同的算法封装成独立的策略类,使得它们可以相互替换,而不影响客户端的使用。策略模式与其他设计模式有一些......