多线程抢票系统浅析

摘要:
作者打算编写一个轻量级版本的Seckill系统,因此需要多线程来模拟客户抢购产品。所以我想写一个简单的多线程抢票系统,以加深我对线程池和同步的理解。每个客户都同步购票。当一个线程正在买票时,其他线程处于等待状态。所有客户线程都已完成购票。主线最终统计售出了多少张票。禁止过度销售。CountDownLatch是一个类,它使主线程能够等待其他线程完成执行。

笔者打算写个轻量版的秒杀系统,那么需要多线程模拟客户去抢购某个商品。故有想先写一个简单的多线程抢票系统加深一下对线程池,同步的理解。

1. 新建Java project,命名为ClientApp1, src文件夹里面新建demo文件夹。

项目结构如下,

 多线程抢票系统浅析第1张

 

2. 程序模拟的场景用例如下,

  1. 多个线程模拟多个客户去购买春运车票
  2. 每个客户购买车票【0,9】,最少买0张,最多能买九张。
  3. 每个客户同步的买票,当某个线程在买票时,其他线程处于等待状态
  4. 所有客户线程买票完毕,主线程最后统计一共卖出多少张车票,切忌不能超卖。
  5. CountDownLatch这个类使主线程等待其他线程各自执行完毕后再执行。

 

3. 代码如下:

package demo;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;

public class Ticket implements Runnable {

    private Integer capacity; // 一共有多少张票
    private Integer soldTickets = 0; // 最后总计售出多少张票
    // CountDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
    // 是通过一个计数器来实现的,计数器的初始值是线程的数量。
    // 每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后处于等待的线程就可以恢复工作了。
    private CountDownLatch latch;
    // 使用Log4j2写日志
    private Logger log;
    
    public void setLog(Logger log) {
        this.log = log;
    }
    
    public Ticket(Integer c, CountDownLatch latch) {
        // TODO Auto-generated constructor stub
        this.capacity = c;
        this.latch = latch;    
    }
    
    public Integer getSoldTickets() {
        return soldTickets;
    }

    @Override
    public synchronized void run() {        
        // 每个线程客户购买0~9张票
        int count = new Random().nextInt(10);
        log.info(Thread.currentThread().getName() + " wants to buy tickets : " + count);
        if(capacity >= count) {
            capacity -= count;
            soldTickets += count;
            log.info(Thread.currentThread().getName() + " has bought tickets successfully. The left tikcets : " + capacity);
        }
        else {
            log.info(String.format("Insufficient tickets[%d], stop trading now.", capacity));
        }
        latch.countDown();    
    }    
}
package demo;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;


public class TicketPractice {

    private static ExecutorService pool;
    private static CountDownLatch latch;
    private static Integer NUMBER = 5000; // 客户线程数目
    private static final Logger logger = LogManager.getLogger(TicketPractice.class);
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        pool = new ThreadPoolExecutor(100, NUMBER, 300, TimeUnit.SECONDS, 
                new LinkedBlockingQueue<Runnable>(NUMBER),Executors.defaultThreadFactory(), 
                new ThreadPoolExecutor.AbortPolicy());
        latch = new CountDownLatch(NUMBER);
        Ticket task = new Ticket(NUMBER, latch);
        task.setLog(logger);
        for(int i=0;i<NUMBER;++i) {
            pool.execute(task);
        }    
        try {
            latch.await();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        logger.info("+++++++++++++++++++++++++++++++++++");
        logger.info("Sold tickets in total : " + task.getSoldTickets());
        logger.info("+++++++++++++++++++++++++++++++++++");
    }
}

4. 值得一提的是,如使用Log4j2,需要引入外部三个jar包

  • log4j-1.2-api-2.12.1.jar
  • log4j-api-2.12.1.jar
  • log4j-core-2.12.1.jar

 

Log4j2.xml内容如下,

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
 <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
 <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="INFO">
    <!--先定义所有的appender -->
    <appenders>
        <!--这个输出控制台的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <ThresholdFilter level="trace" onMatch="ACCEPT"
                onMismatch="DENY" />
            <PatternLayout
                pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" />
        </Console>
        
        <File name="log" fileName="D:/Log/log.txt" append="false">
            <PatternLayout
                pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" />
        </File>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
    <loggers>
        <root level="trace">
            <appender-ref ref="Console" />
            <appender-ref ref="log" />
        </root>
    </loggers>
</configuration>

 

 5. 运行程序,5000个客户线程随机买票,总票数5000张,不能超卖。程序运行日志如下,

22:11:10.317 INFO  demo.Ticket 37 run - pool-2-thread-1 wants to buy tickets : 0
22:11:10.322 INFO  demo.Ticket 41 run - pool-2-thread-1 has bought tickets successfully. The left tikcets : 5000
22:11:10.322 INFO  demo.Ticket 37 run - pool-2-thread-100 wants to buy tickets : 6
22:11:10.322 INFO  demo.Ticket 41 run - pool-2-thread-100 has bought tickets successfully. The left tikcets : 4994
22:11:10.323 INFO  demo.Ticket 37 run - pool-2-thread-99 wants to buy tickets : 5
22:11:10.323 INFO  demo.Ticket 41 run - pool-2-thread-99 has bought tickets successfully. The left tikcets : 4989
。。。。。。
。。。。。。
22:11:11.359 INFO  demo.Ticket 37 run - pool-2-thread-3 wants to buy tickets : 2
22:11:11.359 INFO  demo.Ticket 44 run - Insufficient tickets[0], stop trading now.
22:11:11.365 INFO  demo.TicketPractice 38 main - +++++++++++++++++++++++++++++++++++
22:11:11.366 INFO  demo.TicketPractice 39 main - Sold tickets in total : 5000
22:11:11.366 INFO  demo.TicketPractice 40 main - +++++++++++++++++++++++++++++++++++

免责声明:文章转载自《多线程抢票系统浅析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇30个高质量并且免费的Android图标【Android Icon素材】各种电子面单-Api接口(顺丰、快递鸟、菜鸟)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

.NET异步程序设计——异步委托

目录 1.AMP模式简介 2.使用BeginInvoke实现异步委托 3.原始线程怎么知道新线程已经运行完毕 4.使用AsyncCallback委托实现回调模式 5.源代码下载 shanzm-2020年2月11日 18:55:50 1.AMP模式简介 在.net1.x的版本中就可以使用IAsyncResult接口实现异步操作,但是比较复杂,这种...

Linux平台Cpu使用率的计算

proc文件系统 /proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为内核与进程提供通信的接口。用户和应用程序可以通过/proc得到系统的信息,并可以改变内核的某些参数。由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取/proc目录中的文件时,proc文件系统是动态从系统内核读出所需信息并提交的。 /pr...

JMeter学习(一)工具简单介绍

一、JMeter介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序)。它可以用来测试静态和动态资源的性能,例如:静态文件,Java Servlet,CGI Scripts,Java Object,数据库和FTP服务器等等。JMeter可用于模拟大量负载来测试一台服务器,网络或者对象...

Android 异步加载解决方案

Android的Lazy Load主要体现在网络数据(图片)异步加载、数据库查询、复杂业务逻辑处理以及费时任务操作导致的异步处理等方面。在介绍Android开发过程中,异步处理这个常见的技术问题之前,我们简单回顾下Android开发过程中需要注意的几个地方。 Android应用开发过程中必须遵循单线程模型(Single Thread Model)的原则。因...

python中的多线程编程与暂停、播放音频的结合

先给两个原文链接: https://blog.csdn.net/u013755307/article/details/19913655 https://www.cnblogs.com/scolia/p/6132950.html 播放wav音频的原代码: #引入库 importpyaudio importwave importsys #定义数据流块...

Unity应用架构设计(10)——绕不开的协程和多线程(Part 2)

在上一回合谈到,客户端应用程序的所有操作都在主线程上进行,所以一些比较耗时的操作可以在异步线程上去进行,充分利用CPU的性能来达到程序的最佳性能。对于Unity而言,又提供了另外一种『异步』的概念,就是协程(Coroutine),通过反编译,它本质上还是在主线程上的优化手段,并不属于真正的多线程(Thread)。那么问题来了,怎样在Unity中使用多线程呢...