• 多线程抢票系统浅析


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

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

    项目结构如下,

     

     

    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 - +++++++++++++++++++++++++++++++++++
  • 相关阅读:
    SpringMVC ModelAndView方法与模板传参接收不到问题
    DB2单个DB重启
    DB2 数据库绑定用户授权命令
    [转]sublime配置全攻略
    [转]Sublime text2安装php beautifier
    [转]php及xdebug使用小结
    [转]Pear、PHPUnit安装
    [转]WIN下成功安装PEAR
    [转]PHP单元测试利器:PHPUNIT初探
    [转]sublime使用xdebug调试php
  • 原文地址:https://www.cnblogs.com/sankt/p/11644975.html
Copyright © 2020-2023  润新知