当前位置: 欣欣网 > 码农

三种方式模拟两个线程抢票

2024-04-02码农

(给 哪吒编程 加星标,提高Java技能)

大家好,我是哪吒。

假期来临,抢票出行又将迎来一波新的高潮,今天就来说说在Java中如何高效抢票~

在多线程编程中,资源竞争是一个常见的问题。资源竞争发生在多个线程试图同时访问或修改共享资源时,可能导致数据不一致或其他并发问题。在模拟两个线程抢票的场景中,我们需要考虑如何公平地分配票,并确保每个线程都有机会成功获取票。

本篇文章将通过三种方式来模拟两个线程抢票的过程,以展示不同的并发控制策略。

这三种方式包括:

  • 使用 Synchronized 来确保一次只有一个线程可以访问票资源。

  • 使用 ReentrantLock 来实现线程间的协调。

  • 使用 Semaphore 来限制同时访问票的线程数量。

  • 通过比较这三种方式,我们可以深入了解并发控制的不同实现方式及其优缺点。在实际应用中,需要根据具体场景和需求选择合适的并发控制策略。

    此外,为了更直观地展示抢票过程,我们将使用代码来描述每种方式的实现逻辑。

    一、Synchronized

    含义:Synchronized 是 Java 中的一个关键字,用于实现线程同步。当一个方法或代码块被 Synchronized 修饰时,同一时间只能有一个线程可以执行这个方法或代码块。

    代码如下:

    static classTicketSystemBySynchronized {privateint tickets = 100;publicvoidsellTicket() {while (tickets > 0) { //还有票时进行循环 synchronized (this) {try {if (tickets > 0) System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --tickets); Thread.sleep(200); //模拟售票 } catch (InterruptedException e) { e.printStackTrace(); } } } }}

    这个类中有一个私有的整型变量 tickets,表示票的总数,初始值为 100。

    类中有一个公共方法 sellTicket(),这个方法模拟售票过程。当还有票(tickets > 0)时,会进入一个 while 循环。在循环中,首先通过 synchronized (this) 对当前对象进行同步,保证同一时间只有一个线程可以执行以下代码块。

    在同步代码块中,首先检查票的数量是否大于0。如果是,则输出当前线程的名称以及售出的票数和剩余票数。然后,通过 --tickets 操作将票的数量减1。

    接下来,线程休眠 200 毫秒(模拟售票过程)。休眠结束后,循环继续执行,直到票的数量为 0。

    二、ReentrantLock

    含义:ReentrantLock,也称为可重入锁,是一种递归无阻塞的同步机制。它可以等同于 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更强大、灵活的锁机制,可以减少死锁发生的概率。

    代码如下:

    static classTicketSystemByReentrantLock {privateint tickets = 100;private final ReentrantLock lock = new ReentrantLock(); //定义锁publicvoidsellTicket() {while (tickets > 0) {lock.lock(); //上锁try { Thread.sleep(200); //模拟售票if (tickets > 0) System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --tickets); } catch (InterruptedException e) { e.printStackTrace(); } finally {lock.unlock(); //解锁 } } }}

    这个类中有一个私有的整型变量 tickets,表示票的总数,初始值为 100。 另外定义了一个私有的 final 类型的 ReentrantLock 对象 lock,这个对象用于控制对共享资源的访问。

    类中有一个公共方法 sellTicket(),这个方法模拟售票过程。当还有票(tickets > 0)时,会进入一个 while 循环。在循环中,首先通过 lock.lock() 获取锁,保证同一时间只有一个线程可以执行以下代码块。

    在锁保护的代码块中,首先线程休眠 200 毫秒(模拟售票过程)。然后检查票的数量是否大于 0。如果是,则输出当前线程的名称以及售出的票数和剩余票数。然后,通过 --tickets 操作将票的数量减 1。

    最后,都会通过 lock.unlock() 释放锁。防止死锁!

    三、Semaphore

    含义:Semaphore 是一种计数信号量,用于管理一组资源。它是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理地使用公共资源。Semaphore 内部基于 AQS(Abstract Queued Synchronizer)的共享模式,相当于给线程规定一个量从而控制允许活动的线程数。

    代码如下:

    static classTicketSystemBySemaphore {private final Semaphore semaphore;publicTicketSystemBySemaphore() {this.semaphore = new Semaphore(100); //总共100张票 }publicvoidsellTicket() {int i = semaphore.availablePermits(); //返回此信号量中当前可用的许可证数while (i > 0) {try { Thread.sleep(200); semaphore.acquire(); // 获取信号量,如果信号量为0,线程将阻塞等待 System.out.println( Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --i); } catch (InterruptedException e) {thrownew RuntimeException(e); } finally { semaphore.release(); // 释放信号量,允许其他线程获取信号量 } } }}

    Semaphore 是一个计数信号量,用于控制资源的并发访问。在构造函数中,初始化了这个 Semaphore,设置总的可用票数为 100。

    sellTicket() 方法模拟售票过程。首先获取当前可用的票数,然后进入一个 while 循环,只要还有可用的票,就会尝试获取一个票。如果当前没有可用的票,线程将会阻塞等待。一旦获取了票,就输出售出的信息。最后释放信号量。

    四、抽象工厂模式优化

    含义:抽象工厂模式是一种创建型设计模式,它为创建一系列相关或互相依赖的对象提供了一种最佳解决方案。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。


    因为要对三种实现类型的代码进行测试,不想多写 if...else... 的代码,不想每次指定创建的对象,也为了防止以后有更多实现方法的不方便。提高代码的可维护性和可扩展性。

    所以这里采用抽象工厂模式来进行优化。

    代码如下:

    首先实现一个接口类:

    publicinterfaceTicketSystem {voidsellTicket();}

    因为三个模拟实现中都定义了 sellTicket 这个方法,所以在接口类里面定义一个方法,然后由实现类去重写该方法。

    接下来实现静态工厂类:

    static class CodeSandboxFactory {static TicketSystem newInstance(Stringtype) {switch (type) {case"Synchronized":returnnew TicketSystemBySynchronized();case"ReentrantLock":returnnew TicketSystemByReentrantLock();case"Semaphore":default:returnnew TicketSystemBySemaphore(); } }}

    这个 CodeSandboxFactory 类是一个静态工厂类,用于创建TicketSystem对象的不同实例。它接受一个字符串参数 type,根据该参数的值决定创建哪种类型的TicketSystem 对象。

  • 如果type参数的值为"Synchronized",则返回一个新的 TicketSystemBySynchronized对象;

  • 如果type参数的值为"ReentrantLock",则返回一个新的 TicketSystemByReentrantLock 对象;

  • 如果type参数的值为"Semaphore",则返回一个新的 TicketSystemBySemaphore对象;

  • 如果type参数的值不是以上三种之一,则默认返回一个新的TicketSystemBySemaphore 对象。

  • 这种设计使得客户端代码可以方便地通过传递不同的类型字符串来获取不同类型的 TicketSystem 对象,而不需要关心这些对象的实际创建过程。

    这有助于降低客户端代码与具体实现之间的耦合度,提高代码的可维护性和可扩展性。

    五、整体代码

    代码如下:

    public classThreadsGrabTickets{publicstaticvoidmain(String[] args){ TicketSystem system = CodeSandboxFactory.newInstance("Synchronized");// TicketSystem system =// CodeSandboxFactory.newInstance("ReentrantLock"); TicketSystem// system = CodeSandboxFactory.newInstance("Semaphore");new Thread(system::sellTicket, "线程1").start();new Thread(system::sellTicket, "线程2").start(); }static classCodeSandboxFactory{static TicketSystem newInstance(String type){switch (type) {case"Synchronized":returnnew TicketSystemBySynchronized();case"ReentrantLock":returnnew TicketSystemByReentrantLock();case"Semaphore":default:returnnew TicketSystemBySemaphore(); } } }static classTicketSystemBySynchronizedimplementsTicketSystem{privateint tickets = 100;@OverridepublicvoidsellTicket(){while (tickets > 0) {synchronized (this) {try {if (tickets > 0) System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --tickets); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } }static classTicketSystemByReentrantLockimplementsTicketSystem{privateint tickets = 100;privatefinal ReentrantLock lock = new ReentrantLock(); //定义锁@OverridepublicvoidsellTicket(){while (tickets > 0) { lock.lock(); //上锁try { Thread.sleep(200); //模拟售票if (tickets > 0) System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --tickets); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //解锁 } } } }static classTicketSystemBySemaphoreimplementsTicketSystem{privatefinal Semaphore semaphore;publicTicketSystemBySemaphore(){this.semaphore = new Semaphore(100); //总共100张票 }@OverridepublicvoidsellTicket(){int i = semaphore.availablePermits(); //返回此信号量中当前可用的许可证数while (i > 0) {try { Thread.sleep(200); semaphore.acquire(); // 获取信号量,如果信号量为0,线程将阻塞等待 System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --i); } catch (InterruptedException e) {thrownew RuntimeException(e); } finally { semaphore.release(); // 释放信号量,允许其他线程获取信号量 } } } }}

    六、总结

    本文通过模拟两个线程抢票的场景,展示了三种不同的并发控制策略: 使用 Synchronized、ReentrantLock 和 Semaphore。

    通过比较这三种方式,我们可以深入了解并发控制的不同实现方式。

    在实际应用中,需要根据具体场景和需求选择合适的并发控制策略。

    来源:blog.csdn.net/kologin/article/details/135953580

    (版权归原作者所有,侵删)

    - EOF -

    推荐阅读 点击标题可跳转

    ·················END·················

    看完本文有收获?请转发分享给更多人

    关注「哪吒编程」,提升Java技能

    点赞和在看就是最大的支持 ❤️