前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >三种方式模拟两个线程抢票【最全版】

三种方式模拟两个线程抢票【最全版】

作者头像
绿毛龟
发布2024-02-02 15:19:46
1190
发布2024-02-02 15:19:46
举报
文章被收录于专栏:学习道路指南学习道路指南

前言

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

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

这三种方式包括:

  1. 使用 Synchronized 来确保一次只有一个线程可以访问票资源。
  2. 使用 ReentrantLock 来实现线程间的协调。
  3. 使用 Semaphore 来限制同时访问票的线程数量。

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

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


一、Synchronized

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

代码如下:

代码语言:javascript
复制
static class TicketSystemBySynchronized {
        private int tickets = 100;

        public void sellTicket() {
            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更强大、灵活的锁机制,可以减少死锁发生的概率。

代码如下:

代码语言:javascript
复制
 static class TicketSystemByReentrantLock {
        private int tickets = 100;

        private final ReentrantLock lock = new ReentrantLock();//定义锁

        public void sellTicket() {
            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)的共享模式,相当于给线程规定一个量从而控制允许活动的线程数。

代码如下:

代码语言:javascript
复制
 static class TicketSystemBySemaphore {
        private final Semaphore semaphore;

        public TicketSystemBySemaphore() {
            this.semaphore = new Semaphore(100);//总共100张票
        }

        public void sellTicket() {
            int i = semaphore.availablePermits();//返回此信号量中当前可用的许可证数

            while (i > 0) {
                try {
                    Thread.sleep(200);
                    semaphore.acquire(); // 获取信号量,如果信号量为0,线程将阻塞等待
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    semaphore.release(); // 释放信号量,允许其他线程获取信号量
                }
            }
        }
    }

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

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


四、抽象工厂模式优化

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

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

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

代码如下:

首先实现一个接口类:

代码语言:javascript
复制
public interface TicketSystem {
    void sellTicket();
}

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

接下来实现静态工厂类:

代码语言:javascript
复制
 static class CodeSandboxFactory {
        static TicketSystem newInstance(String type) {
            switch (type) {
                case "Synchronized":
                    return new TicketSystemBySynchronized();
                case "ReentrantLock":
                    return new TicketSystemByReentrantLock();
                case "Semaphore":
                default:
                    return new TicketSystemBySemaphore();
            }
        }
    }

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

  • 如果type参数的值为"Synchronized",则返回一个新的TicketSystemBySynchronized对象;
  • 如果type参数的值为"ReentrantLock",则返回一个新的TicketSystemByReentrantLock对象;
  • 如果type参数的值为"Semaphore",则返回一个新的TicketSystemBySemaphore对象;
  • 如果type参数的值不是以上三种之一,则默认返回一个新的TicketSystemBySemaphore对象。

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

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


五、整体代码

代码如下:

代码语言:javascript
复制
public class ThreadsGrabTickets {
    public static void main(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 class CodeSandboxFactory {
        static TicketSystem newInstance(String type) {
            switch (type) {
                case "Synchronized":
                    return new TicketSystemBySynchronized();
                case "ReentrantLock":
                    return new TicketSystemByReentrantLock();
                case "Semaphore":
                default:
                    return new TicketSystemBySemaphore();
            }
        }
    }

    static class TicketSystemBySynchronized implements TicketSystem {
        private int tickets = 100;

        @Override
        public void sellTicket() {
            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 class TicketSystemByReentrantLock implements TicketSystem {
        private int tickets = 100;

        private final ReentrantLock lock = new ReentrantLock();//定义锁

        @Override
        public void sellTicket() {
            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 class TicketSystemBySemaphore implements TicketSystem {
        private final Semaphore semaphore;

        public TicketSystemBySemaphore() {
            this.semaphore = new Semaphore(100);//总共100张票
        }

        @Override
        public void sellTicket() {
            int i = semaphore.availablePermits();//返回此信号量中当前可用的许可证数

            while (i > 0) {
                try {
                    Thread.sleep(200);
                    semaphore.acquire(); // 获取信号量,如果信号量为0,线程将阻塞等待
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩余票数:" + --i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    semaphore.release(); // 释放信号量,允许其他线程获取信号量
                }
            }
        }
    }
}

六、总结

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

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

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-02-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、Synchronized
  • 二、ReentrantLock
  • 三、Semaphore
  • 四、抽象工厂模式优化
  • 五、整体代码
  • 六、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档