认真分析题目要求后,我在TicketingDS类作为代码主体,调用其他个模块:SeatSection
、RouteSection、CoachSection,分别代表车次、车厢、座位。以及Test测试模块。
1.定义私有属性
private final int routeNum;// 车次总数
private final int stationNum;// 车站总数
private ArrayList routeArray;//车次列表
ArrayList是Java集合框架中的一个重要的类,它继承于AbstractList,实现了List接口,是一个长度可变的集合,提供了增删改查的功能。集合中允许null的存在。ArrayList类还是实现了RandomAccess接口,可以对元素进行快速访问。实现了Serializable接口,说明ArrayList可以被序列化,还有Cloneable接口,可以被复制。和Vector不同的是,ArrayList不是线程安全的。
2.依次遍历每个车次
for (int routeId = 1; routeId <= routeNum; routeId++)
this.routeArray.add(new RouteSection(routeId, coachNum, seatNum));
}
3.买票
使用buyTicket方法,先判断车次和车站是否在范围内,再调用RouteSection模块里的initSeal方法,尝试购票,并返回(route - 1)。
4.查询
先判断车次和车站是否在范围内,再调用RouteSection模块里的initInquiry方法尝试购票,并返回(route - 1)。
5.退票
先获取车票的车次,判断车票和车次是否在范围内,再调用RouteSection模块里的initRefund方法尝试购票,并返回(route - 1)。
具体实现TicketingDS类里的三个方法。
1.先定义属性:
private final int routeId;//车次序号
private final int coachNum;//车厢数目
private ArrayList coachList;//车厢列表
private AtomicLong ticketId;//车票的票号,每个车票有唯一的票号
private Queue queue_SoldTicket;//构造队列
2.使用构造方法,当java类实例化时,输入参数值,将属性初始化。
3.使用queue_SoldTicket构造队列。
this.queue_SoldTicket = new ConcurrentLinkedQueue();
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法来实现
4.遍历车厢。
5.使用initSeal方法尝试购票。建立车厢的数据结构使用了:
int randCoach = ThreadLocalRandom.current().nextInt(this.coachNum);
建立车厢的数据结构,其中ThreadLocalRandom是线程相关的,调ThreadLocalRandom.current()会返回当前线程的ThreadLocalRandom对象。ThreadLocalRandom优点在于高并发条件下,不同线程产生的随机数能不一致。同样的,调用nextInt方法也会返回一个伪随机整数值。
使用getAndIncrement方法,以原子方式将当前值加 1,返回旧值(即加1前的原始值)。保证了操作的原子性。
6.使用initInquiry方法,查询余票情况,也使用getAndIncrement方法来保证原子性。
7.退票使用initRefund方法。建立哈希表,根据对象的地址或者字符串或者数字算出对应的int类型的数值。contains判断是否包含这张票,找到后然后删除票,再重新返回。也使用getAndIncrement方法来保证原子性。
同RouteSection.java。
先定义变量,建立座位列表,存储座位的情况。
尝试购票,以及查询,退票。
同RouteSection.java。
先定义变量,建立座位列表,存储座位的情况。
尝试购票,以及查询,退票。
这里为了验证座位是否空闲,使用了如下方法:
private AtomicLong availableSeat;
根据一个 AtomicLong 型 64 位的 availableSeat判断是否空闲。availableSeat 的每一位表示座位对应的每一站, 0 表示未售出, 1 表示售出。购票查询退票时均采用从 route->coach->seat 的方式调用方法,在 seatNode 操作时,用原语 compareAndSet 构造非阻塞式的自旋锁来保证并发操作的原子性。
test方法参考了网上提供的思路和讲解。
先设置测试数值:
private final static int ROUTE_NUM = 5;// 列车车次
private final static int COACH_NUM = 8;// 车箱数
private final static int SEAT_NUM = 100;// 每个车厢的座位数
private final static int STATION_NUM = 10;// 总站数
private final static int TEST_NUM = 10000;// 每个线程里调用的方法数是10000次
private final static int refund = 10;// 退票数目
private final static int buy = 40;// 买票数目
private final static int query = 100;// 查询票数目
private final static int thread = 64;// 线程数目
再获取乘客信息。
在main方法里,先设置不同的线程总数,再对不同threadNums数目的线程进行处理,并发完成threadNums数目的所有线程。在Thread构造函数中传入Runnable实现对象,在Thread源码中将Runnable对象传递给init方法。
运行线程,使用getAndIncrement方法将原值+1,并且返回+1前的原值。获取id。
根据退票/购票/查询余票=1/3/6的比例进行测试。
运行老师提供的verify测试单线程,通过,并显示Verification Finished。
使用trace.java生成如下结果:
使用自己准备的test.java生成如下结果,来测试性能:
综上,测试合格。
使用buyTicket方法,先判断车次和车站是否在范围内,再调用RouteSection模块里的initSeal方法,尝试购票,并返回(route - 1)。
//买票
public Ticket buyTicket(String passenger, int route, int departure,int arrival) {
//先判断车次和车站是否在范围内
if (route <= 0 || route > this.routeNum || arrival > this.stationNum
|| departure >= arrival)
return null;
//尝试购票,并返回(route - 1)
return this.routeArray.get(route - 1).initSeal(passenger, departure,
arrival);
}
从RouteSection到CoachSection,再到SeatSection,进一步的具体购票:(SeatSection)
public int initSeal(final int departure, final int arrival) {
long oldAvailSeat = 0;
long newAvailSeat = 0;
long temp = 0;
int i = departure - 1;
while(i < arrival - 1){
long pow = 1;
pow = pow << i;
temp |= pow;
i++;
}
do {
oldAvailSeat = this.availableSeat.get();
long result = temp & oldAvailSeat;
if (result != 0) {
return -1;
}
else {
newAvailSeat = temp | oldAvailSeat;
}
} while (!this.availableSeat.compareAndSet(oldAvailSeat, newAvailSeat));
return this.seatId;
}
这里使用了compareAndSet,实现了区别于synchronouse同步锁的一种乐观锁,使用这些类在多核CPU的机器上会有比较好的性能。它的作用是将指定内存地址的内容与所给的某个值相比,如果相等,则将其内容替换为指令中提供的新值,如果不相等,则更新失败。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
先判断车次和车站是否在范围内,再调用RouteSection模块里的initInquiry方法尝试购票,并返回(route - 1)。
//查询
public int inquiry(int route, int departure, int arrival) {
//先判断车次和车站是否在范围内
if (route <= 0 || route > this.routeNum || arrival > this.stationNum
|| departure >= arrival)
return -1;
//尝试查询,并返回(route - 1)
return this.routeArray.get(route - 1).initInquiry(departure, arrival);
}
从RouteSection到CoachSection,再到SeatSection,进一步的具体购查询:(SeatSection)
public int initInquiry(final int departure, final int arrival) {
long oldAvailSeat = this.availableSeat.get();
long temp = 0;
long pow;
int i = departure - 1;
while(i < arrival - 1){
pow = 1;
pow = pow << i;
temp |= pow;
i++;
}
long result = temp & oldAvailSeat;
return (result == 0) ? 1 : 0;
}
先获取车票的车次,判断车票和车次是否在范围内,再调用RouteSection模块里的initRefund方法尝试购票,并返回(route - 1)。
//退票
public boolean refundTicket(Ticket ticket) {
//获取车票的车次
final int routeId = ticket.route;
//先判断车票和车次是否在范围内
if (ticket == null || routeId <= 0 || routeId > this.routeNum)
return false;
//尝试退票,并返回(route - 1)
return this.routeArray.get(routeId - 1).initRefund(ticket);
}
从RouteSection到CoachSection,再到SeatSection,进一步的具体退票:(SeatSection)
public boolean initRefund(final int departure, final int arrival) {
long oldAvailSeat = 0;
long newAvailSeat = 0;
long temp = 0;
int i = departure - 1;
while(i < arrival - 1){
long pow = 1;
pow = pow << i;
temp |= pow;
i++;
}
temp = ~temp;
do {
oldAvailSeat = this.availableSeat.get();
newAvailSeat = temp & oldAvailSeat;
} while (!this.availableSeat.compareAndSet(oldAvailSeat, newAvailSeat));
return true;
}
}
这里再次使用了compareAndSet,来实现退票的方法。
对于可线性化。可以实现并行操作时时序重叠,实际按可线性化点的顺序来执行。我使用的是cas,它的作用是将指定内存地址的内容与所给的某个值相比,如果相等,则将其内容替换为指令中提供的新值,如果不相等,则更新失败。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。买票、查询、退票都满足可线性化。
对于无死锁。申请锁的线程最终都会获得锁,所以满足无死锁。
对于无饥饿。我使用的是cas,每个申请锁的线程都会获得锁,并且所有的线程都能在有限步当中完成,因此必然不会有线程永久地呆在临界区内出不去,所以它一定是无饥饿的。所以无饥饿。
对于无锁。我的某一个方法调用线程,最终都会返回,保证至少有一个线程能够在有限步当中完成它的操作,所有的线程在不停地竞争直到有一个胜出为止。所以是无锁的。
对于无等待。相比于无锁更进一步,它首先要求是无锁的,保证所有线程能进并且至少有一个线程能出来,它要求所有进入临界区的线程都能够在有限步当中完成其操作。这个要求很高,因为任何线程都能够无障碍进入临界区,并且任何线程都能够在有限步当中完成操作后离开临界区。调用一个方法的线程最终都会返回,所以无等待。
综上,均满足要求。
码字不易,都看到这里了不如点个赞哦~
我还写了很多文章,欢迎关注我哦~ 一起加油~