笔者打算写个轻量版的秒杀系统,那么需要多线程模拟客户去抢购某个商品。故有想先写一个简单的多线程抢票系统加深一下对线程池,同步的理解。
1. 新建Java project,命名为ClientApp1, src文件夹里面新建demo文件夹。
项目结构如下,
2. 程序模拟的场景用例如下,
- 多个线程模拟多个客户去购买春运车票
- 每个客户购买车票【0,9】,最少买0张,最多能买九张。
- 每个客户同步的买票,当某个线程在买票时,其他线程处于等待状态
- 所有客户线程买票完毕,主线程最后统计一共卖出多少张车票,切忌不能超卖。
- 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 - +++++++++++++++++++++++++++++++++++