多线程抢票系统浅析

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

 

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 - +++++++++++++++++++++++++++++++++++

 

posted @ 2019-10-09 22:54  沐璟  阅读(1932)  评论(0编辑  收藏  举报