下载 http://download.csdn.net/download/crazyzxljing0621/9969870

前言

10.1要去苏州玩。

9月初去携程买票,发现过几天才放票

现在可以预约抢票。

用户流程 : 选票 -> 选预约 -> 选抢票类型极速/快速/免费  -> 支付票价和抢票费用  -> 自动帮抢票

携程这个流程我猜测是(当然我没有做过和12306或什么票务接口对接的工作  )

  1. 记录用户选票类型和票信息,等放票的时候通过接口调用买票这个接口可能是多次提交或提交一次设定要购买的票信息数组?
  2. 得到票后将票分配给所有预付款用户,当然按照抢票类型优先分配,按照如果一个人一次买了3张票,优先分配邻座。
  3. 因为很少买火车票,百度说火车票是分批次放票,比如13点100张票,14点200张票这样。。
  4. 所以只要还有放票的可能携程还是继续给未得到票的人抢票,即便放票完毕。还有退票不是么

恰逢正在看juc,觉得这个可以做一个小demo来巩固一下

我的demo简单多了

  1. 设定N个用户
  2. N个用户全部预约就位
  3. 系统开始放票M张,总共会放Y张票
  4. 每放票M张,则用户开始同时去抢票
  5. 抢不到的等着继续放票
  6. 放票系统发现M张票强没了,会继续放票
  7. 用户也继续抢票
  8. 最后放票完毕,没有余票了。结束程序,记录抢票成功的用户

目录结构

代码

先看看最终结果
总共21张票,25个人同时并发去抢,每次放票5张,每隔几秒再次放票,直到票售罄
买票用户 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25] | 就位
sys : 开始放票
/**
**票仓[[票0, 票1, 票2, 票3, 票4]]
**增加了5张票
**/
用户[25]的抢票结果为 true[票0]
用户[2]的抢票结果为 true[票1]
用户[4]的抢票结果为 true[票2]
用户[6]的抢票结果为 true[票3]
用户[8]的抢票结果为 true[票4]
/**
**票仓[[票5, 票6, 票7, 票8, 票9]]
**增加了5张票
**/
用户[10]的抢票结果为 true[票5]
用户[12]的抢票结果为 true[票6]
用户[14]的抢票结果为 true[票7]
用户[16]的抢票结果为 true[票8]
用户[18]的抢票结果为 true[票9]
/**
**票仓[[票10, 票11, 票12, 票13, 票14]]
**增加了5张票
**/
用户[20]的抢票结果为 true[票10]
用户[22]的抢票结果为 true[票11]
用户[24]的抢票结果为 true[票12]
用户[1]的抢票结果为 true[票13]
用户[3]的抢票结果为 true[票14]
/**
**票仓[[票15, 票16, 票17, 票18, 票19]]
**增加了5张票
**/
用户[5]的抢票结果为 true[票15]
用户[9]的抢票结果为 true[票16]
用户[7]的抢票结果为 true[票17]
用户[13]的抢票结果为 true[票18]
用户[15]的抢票结果为 true[票19]
/**
**票仓[[票20]]
**增加了1张票
**/
用户[17]的抢票结果为 true[票20]
放票完毕
买票用户 [0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 25] | 抢票成功
package juc.demo;
/*** 抢票的核心类* @author Allen 2017年9月7日**/import java.util.Arrays;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;import juc.demo.runnable.GrabThread;
import juc.demo.runnable.TicketSupplement;/*** 抢票* * @author Allen 2017年9月7日**/
public class GrabTicket {public int TICKET_INDEX = 0;// 票仓当前指针ReentrantLock reentrantlock = new ReentrantLock();// 独占锁Condition condition = reentrantlock.newCondition();// 在AQS中与独占锁配合使用,作为了队列的存在private CyclicBarrier cyclicBarrier;// 建立一个同步屏障public GrabTicket() {this.cyclicBarrier = new CyclicBarrier(Data.USER_NUM, new Runnable() {@Overridepublic void run() {Temp.print("就位"); //当所有用户await后,打印已就位的用户列表System.out.println("sys : 开始放票");addTicket(); //向当前票仓填票}});}public void execute() {ExecutorService threadPool = Executors.newFixedThreadPool(Data.USER_NUM); //启动一个固定长度的线程池来维护for (int i = 0; i < Data.USER_NUM; i++) {threadPool.execute(new GrabThread(cyclicBarrier, this, i + 1)); //每个抢票的用户}threadPool.execute(new TicketSupplement(this));//启动补票线程threadPool.shutdown();}/*** 放票*/public void addTicket() {TICKET_INDEX = 0; //每次放票把放票指针归零 Data.refresh(); //刷新新票到仓库System.out.println("/**");System.out.printf("**票仓[%s]\n", Arrays.toString(Data.TICKETS_NOW));System.out.printf("**增加了%s张票\n", Data.TICKETS_NOW.length);System.out.println("**/");}public void signalAll() { try {reentrantlock.lock(); //condition.signalAll();//独占锁确保可以把reentrantlock中condition队列阻塞的线程全部解阻塞,类似 wait noityAll} finally {reentrantlock.unlock();}}//独占锁确保了同步性,并使用condition来使未得到票的线程,来等待放票后唤醒         public void Grab(int i) throws InterruptedException {String ticketName = null;reentrantlock.lock();try {if (TICKET_INDEX >= Data.TICKETS_NOW.length) { //当前指针超过了容器大小则进入阻塞等待下一次放票condition.await();  //Condition将独占锁中满足条件的放入阻塞队列进行等待,直到signal唤醒Grab(i);//递归来进入Grab抢票} else {ticketName = Data.TICKETS_NOW[TICKET_INDEX++];//通过指针获得当前票号Data.TICKETS_NOW[TICKET_INDEX - 1] = null;//将得到的票设为null标识已经被买走 System.out.printf("用户[%s]的抢票结果为 %s[%s] \n", i, ticketName != null, ticketName);Temp.add(i);//将抢票成功的用户添加到数组中Data.USER_SUM++;//记录抢完票的用户,主要用于当用户数小于票数,放票机进行拦截}} finally {reentrantlock.unlock();//释放锁}}
}
//此类提供启动入口,用户初始化,票仓初始化,以及购票等主要函数 
package juc.demo.runnable;import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;import juc.demo.GrabTicket;
import juc.demo.Temp;/*** 抢票线程* * @author Allen 2017年9月7日**/
public class GrabThread implements Runnable {CyclicBarrier cb;GrabTicket grabTicket;int i;public GrabThread(CyclicBarrier cb, GrabTicket grabTicket, int i) {this.cb = cb;this.grabTicket = grabTicket;this.i = i;}@Overridepublic void run() {Temp.add(i);//添加用户到临时容器try {   cb.await(); //进入await等待,当等待人数满足CyclicBarrier构造传入的num时,condition会唤醒所有awaitgrabTicket.Grab(i);//与其他线程同时购票} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}
}
package juc.demo.runnable;import java.util.Arrays;import juc.demo.Data;
import juc.demo.GrabTicket;
import juc.demo.Temp;/*** 放票线程* * @author Allen 2017年9月7日**/
public class TicketSupplement implements Runnable {GrabTicket grabTicket;public TicketSupplement(GrabTicket grabTicket) {this.grabTicket = grabTicket;}@Overridepublic void run() {for (;;) {try {Thread.sleep(1000);if (Arrays.asList(Data.TICKETS_NOW).stream().allMatch(this::isNull)) { //确保没票了才放票,网上说火车票是每隔一个时间段就放票。这里增加了没票才放票的逻辑是因为我这里是个demoif (Data.TICKET_INDEX * Data.TICKET_MAX > Data.TICKET_COUNT) {//判断放票是否越界System.out.println("放票完毕");Temp.print("抢票成功");System.exit(-1);} else {grabTicket.addTicket();//放票grabTicket.signalAll();// 补票完毕把condition队列中的等待全部唤醒}} else if(Data.USER_SUM  == Data.USER_NUM){//如果得到票的人和总人数相匹配,则证明没票了。那么这个模拟放票进程就可以关闭了System.out.printf("都抢完票了 %s : [%s张]\n", "余票",Arrays.asList(Data.TICKETS_NOW).stream().filter(this::isNotNull).count());System.exit(-1);}} catch (InterruptedException e) {e.printStackTrace();}}}private boolean isNull(String str) { //方法引用的2个函数return str == null;}private boolean isNotNull(String str) {return !isNull(str);}}

主要就是上面这2个线程与抢票的核心类来提供整个demo服务

1.模拟开始抢票
2.模拟初始化票仓
3.模拟同时去让用户抢票
4.票仓一瞬间空了
5.模拟下一个时间段继续放票
6.模拟用户继续抢票
如果票比人多则输出
买票用户 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] | 就位
sys : 开始放票
/**
**票仓[[票0, 票1, 票2, 票3, 票4]]
**增加了5张票
**/
用户[19]的抢票结果为 true[票0]
用户[2]的抢票结果为 true[票1]
用户[1]的抢票结果为 true[票2]
用户[18]的抢票结果为 true[票3]
用户[4]的抢票结果为 true[票4]
/**
**票仓[[票5, 票6, 票7, 票8, 票9]]
**增加了5张票
**/
用户[20]的抢票结果为 true[票5]
用户[6]的抢票结果为 true[票6]
用户[8]的抢票结果为 true[票7]
用户[10]的抢票结果为 true[票8]
用户[12]的抢票结果为 true[票9]
/**
**票仓[[票10, 票11, 票12, 票13, 票14]]
**增加了5张票
**/
用户[14]的抢票结果为 true[票10]
用户[16]的抢票结果为 true[票11]
用户[5]的抢票结果为 true[票12]
用户[3]的抢票结果为 true[票13]
用户[7]的抢票结果为 true[票14]
/**
**票仓[[票15, 票16, 票17, 票18, 票19]]
**增加了5张票
**/
用户[9]的抢票结果为 true[票15]
用户[11]的抢票结果为 true[票16]
用户[13]的抢票结果为 true[票17]
用户[15]的抢票结果为 true[票18]
用户[17]的抢票结果为 true[票19]
/**
**票仓[[票20]]
**增加了1张票
**/
都抢完票了 余票 : [1张]

其他类

package juc.demo;import java.util.Arrays;/*** 模拟用户组* * @author Allen 2017年9月7日**/
public class Temp {//为了输出用户组做了一个temp来打印到consoleprivate static int[] temp = new int[Data.USER_NUM];private static int tempIndex = 0;public static void add(int val) {temp[tempIndex++] = val;}public static void print(String str) {//这里用快排做了个升序 当然因为new int[xx]肯定会有0位(如果票比人少),这里没做去0处理。太麻烦了demo而已//快速排序用的是我上上篇《六种常见代码》中的类//http://blog.csdn.net/crazyzxljing0621/article/details/77867788System.out.printf("买票用户 %s | %s\n", Arrays.toString(QuickSort.execute(temp)),str);temp = new int[Data.USER_NUM]; tempIndex=0;}
}
package juc.demo;/*** 配置* * @author Allen 2017年9月7日**/
public class Data {/** class Constant **///一些数据的初始化public final static int USER_NUM = 20;// 抢票用户总数public static int USER_SUM = 0;// 抢票完毕的用户public final static int TICKET_COUNT = 21;// 总票数public final static int TICKET_MAX = 5;// 票仓容量public static String[] TICKETS_NOW;// 当前可买票仓public static int TICKET_INDEX = 0;// 当前仓页索引private final static String[] TICKETS = new String[TICKET_COUNT];// 总票仓static {for (int i = 0; i < TICKET_COUNT; i++)TICKETS[i] = new StringBuffer("票").append(i).toString(); //把票都初始化到总票仓}/*** 刷新可买票仓*/public static void refresh() { //这里和分页一样了int s = TICKET_INDEX * TICKET_MAX;  int e = s + TICKET_MAX > TICKET_COUNT ? TICKET_COUNT : s + TICKET_MAX;TICKETS_NOW = new String[e - s];for (int i = s, j = 0; i < e; i++, j++) {//每次只拿到当前“页”的票TICKETS_NOW[j] = TICKETS[i];}TICKET_INDEX++;}
}

总结

写这个demo的意义是什么呢?
主要是借助携程带来的一点业务灵感。用CyclicBarrier,reentrantlock,condition来完成同时购买,同步购买的功能

JUC系列之模拟抢票(N人同时抢票,票不足系统补仓,N-M人继续抢票)相关推荐

  1. JUC系列(二)回顾Synchronized关键字

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

  2. 难过!能不能放下抢票套路,我只想买好好回家过年!我在网上抢火车票:多加了100元的加速包,却依然买不到票...

    点击上方 "程序员小乐"关注, 星标或置顶一起成长 每天凌晨00点00分, 第一时间与你相约 每日英文 There is a time in life that is full o ...

  3. JUC系列(一)什么是JUC?

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

  4. 并发编程JUC系列及部分问题

    什么是 CAS 吗? CAS(Compare And Swap)指比较并交换.CAS算法CAS(V, E, N)包含 3 个参数,V 表示要更新的变量,E 表示预期的值,N 表示新值.在且仅在 V 值 ...

  5. JUC系列(十一) | Java 8 CompletableFuture 异步编程

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

  6. JUC系列(十) | ForkJoin框架 并行处理任务

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

  7. JUC系列(九)| ThreadPool 线程池

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

  8. JUC系列(八)| 读写锁-ReadWriteLock

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

  9. JUC系列(六) | Callable和Future接口详解使用、FutureTask应用 获取异步线程返回值

    多线程一直Java开发中的难点,也是面试中的常客,趁着还有时间,打算巩固一下JUC方面知识,我想机会随处可见,但始终都是留给有准备的人的,希望我们都能加油!!! 沉下去,再浮上来,我想我们会变的不一样 ...

最新文章

  1. innerText与innerHTML的区别
  2. 利用st_geometry进行图形叠加分析--结合mybatis
  3. iPhone X掉漆愈演愈烈?手机变成刮刮乐
  4. 还是分了的好——看惠普、赛门铁克拆分
  5. mysql的锁是公平的么_lock 默认公平锁还是非公平锁?公平锁是如何定义?如何实现...
  6. Docker基础(2)——基础操作
  7. 谈论源码_当我们谈论软件“替代品”时,这是什么意思?
  8. Excel控件 Spire.XLS系列教程(2):C# 设置现有 Excel 图表的数据标签样式
  9. 七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)...
  10. Spring学习笔记(入门)
  11. GWR学习笔记(一)
  12. VMware Workstation 英文改中文界面
  13. Linux 安装.deb软件包之前 dpkg-deb 命令查看.deb文件详细信息 版本号 包名
  14. ca42a_demo_c++_new_delete表达式
  15. 【Python实战】批量爬取微博素材,一分钟百张大图自动下载
  16. 被指将赴美上市的雪球:累计融资近3亿美元,股东股权已全部出质
  17. 如何实现游戏中的段位排行榜?
  18. macbook上好用的解压软件_好用的Mac解压软件推荐
  19. 鸿蒙系统是谎言,鸿蒙系统可能是一个善意的谎言,任正非把余承东立的flag拆了...
  20. APP自动化定位方法

热门文章

  1. 750-连接一个 IP 不存在的主机时,握手过程是怎样的?
  2. 既不回头,何必不忘; 既然无缘,何需誓言。 今日种种,似水无痕; 明夕何夕,君已陌路。
  3. QT多线程接收串口数据
  4. iOS项目开发实战——Label中字体字号与Label多行显示
  5. 10省145家企业亮相故宫展示“中华老字号”
  6. 山西检察机关出重拳助力污染防治 涉大气水危险废物等领域
  7. QT模态对话框及非模态对话框学习
  8. 刘海屏全屏显示(主要是隐藏状态栏且布局在状态栏位置上生效)
  9. 计算机信息技术优化数学教学,巧用信息技术,优化小学数学课堂教学活动
  10. JavaScript运算符——一元、二元、三元运算符