用多线程模拟5000人抢1000张票, 测试volatile, synchronized, vector的效用

想要模拟的情况:

1. 总共出票5轮,每次出票200张,本轮票售完后才出下一轮的票,共出票1000张,(票号1-1000),每张票30元,最后商家收到30000元,商家准备票的时候不允许买家订票

 2. 5000个人抢票,每人限购一张,购票分为锁定和付款,锁定后可以决定是否付款(默认50%的人放弃付款),如果不付款则放弃的票重新接受预定,并可以继续抢票.

 3. 售票完成后统计结果:如果出票1000张,商家收到30000元,打印票号和购买人姓名,票号唯一且购票人不重复则运行正确

     PS . 代码里人数和票数都是变量,可以设定1亿人抢2000万张票....注意电脑的高温警报 


方法:

1.  实体类Class Ticket用于记录票号和购买者姓名.

2.  制作二个Ticket的集合list和listPaid, list用于放置可供锁定的票,锁定后从list移除并暂存一个对象里,付款后将购买者姓名写入并放到listPaid里.如果决定不付款,放回list

3.  每一个buyer都是一个线程,购买环节包括

     1) 锁定票号(需要同步,等商家准备好票了,才能锁定,类似电商的开卖时间)

     2) 决定是否付款(不需要同步,实际生活中允许至少几十分钟的时间,同步的话占用太大资源) 

     3) 支付(需要同步,商户账户金额不能被同时修改) 

     4) 将付款过的票放到另一个集合 listPaid (需要同步,否则容易丢失数据)

4. 创建一个线程用来管理整个活动并打印最后的报表


心得与技巧:


1.     如果将整个购票环节全部锁定(相当于一个买家完成所有行为后,才允许另一个买家开始订票),则线程安全,但是锁定的过程太长了无效率, 实务上不可行.

2.     如果要分开上锁, 在付款的环节使用volatile 效果不好,时常会出错, 还是需要用synchronized上锁. 在支付成功后,将付款后的Ticket传到 listPaid集合时,多线程的情况下用      

         ArrayList和LinkedList会出错. 用Vector不会出错 (当 listPaid 只允许添加和访问不允许修改或删除)

3.    多线程同时修改一个集合,如果修改后的对象不再需要被其他线程访问,最好不要直接修改,先用remove丛集合并带到另一个对象里,修改后再放到一个新的集合

4.     一个run方法里最好只有一个while(true)循环,不然可能会阻塞

5.     启动wait的线程,注意启动时notify尚未被执行,否则可能永远等不到到讯号

6.  可以用synchronized的方法让线程去等待一个永远不会出现的notify,达到类似终止线程的效果

7. volatile没法保证安全, synchronized 最能保证安全但是如果一个方法里有太多的 synchronized 容易阻塞, vector在不删除集合元素的情况下安全


错误种类:

1. 计算错误:

     如果付款只用volatile修饰,没有用synchronized同步会出错, 图例卖出1000张票,应收款300000元,实际只有29970元.

*************************************************************************************************

   

*************************************************************************************************

2. 空指针

   提交listPaid(多线程修改arraylist集合)没有同步,会有空指针错误,如上图图例中集合里有1000个元素(票),但是前3个是空值,表示提交失败,导致我们只有997张票

   的购买者讯息(null的数量及出现的时间是随机的)





3. 重复的票号或是购买者(添加集合元素时产生的错误)

    最后的提示”出错了”表示集合里有重复的票号或购买者讯息,这是错误的,因为我们限定每张票号都是唯一,且每人限购一张. 将控制台的讯息复制到word文档检

     查,969的票只有上传一次但集合里有2个元素.




运行结果(节省版面把参数调为 20个买家抢6张票)

抢票活动开始
20个买家创建完成
商家启动
启动买家群
开始准备下一批票了
总票数6
已出票0
本次出票2
准备开始抢票啦
没票了再叫我
买家姓名编号10锁定票号:1
买家姓名编号9锁定票号:2
商家起床
开始准备下一批票了
总票数6
已出票2
本次出票2
准备开始抢票啦
没票了再叫我
买家姓名编号14锁定票号:3
买家姓名编号13锁定票号:4
商家起床
开始准备下一批票了
总票数6
已出票4
买家姓名编号9放弃购买2
买家姓名编号13放弃购买4
买家姓名编号10放弃购买1
买家姓名编号14放弃购买3
本次出票2
准备开始抢票啦
没票了再叫我
买家姓名编号20锁定票号:1
买家姓名编号20付款了,上传票号1上传姓名买家姓名编号20
买家姓名编号19锁定票号:4
买家姓名编号19放弃购买4
买家姓名编号18锁定票号:3
买家姓名编号18放弃购买3
买家姓名编号17锁定票号:2
买家姓名编号17放弃购买2
买家姓名编号17锁定票号:5
买家姓名编号17付款了,上传票号5上传姓名买家姓名编号17
买家姓名编号2锁定票号:6
买家姓名编号2付款了,上传票号6上传姓名买家姓名编号2
买家姓名编号3锁定票号:4
买家姓名编号3放弃购买4
买家姓名编号4锁定票号:3
买家姓名编号4放弃购买3
买家姓名编号15锁定票号:2
买家姓名编号15付款了,上传票号2上传姓名买家姓名编号15
买家姓名编号5锁定票号:4
买家姓名编号5放弃购买4
买家姓名编号7锁定票号:3
买家姓名编号7放弃购买3
买家姓名编号6锁定票号:4
买家姓名编号6付款了,上传票号4上传姓名买家姓名编号6
买家姓名编号8锁定票号:3
买家姓名编号8付款了,上传票号3上传姓名买家姓名编号8
商家起床
活动结束了
商家收款180元
卖出票6张
打印未排序的listPait
开始准备下一批票了
1 买家姓名编号20
5 买家姓名编号17
6 买家姓名编号2
2 买家姓名编号15
4 买家姓名编号6
3 买家姓名编号8
依照票号打印listPaid
1 买家姓名编号20
2 买家姓名编号15
3 买家姓名编号8
4 买家姓名编号6
5 买家姓名编号17
6 买家姓名编号2
依照购票者姓名打印listPaid
2 买家姓名编号15
5 买家姓名编号17
6 买家姓名编号2
1 买家姓名编号20
4 买家姓名编号6
3 买家姓名编号8
结果复核正确
商家收款180元
卖出票6张





最后上代码:

import java.util.Collections;
import java.util.List;
import java.util.Vector;



public class ticketOrderGame_Vector {
	static volatile int sellerBalance=0;//商家账户用于收取票款,同一时间只允许一个线程修改
	static int ticketPrice=30;//设定票价
	static int batchNum=200;//设定每批出票的数量
	static int totalTicketNum=1000;//设定总票数
	static Vector<Ticket> list=new Vector<>();//未付款票的商品架
	static Vector<Ticket> listPaid=new Vector<>();//已付款票
	
	static int setBuyerNum=5000;  //设定买家群数量
	static boolean end=false;  //用于检查是否全部票都卖完并付款
	
	public static void main(String[] args) throws InterruptedException{
		
		new Guard().start(); //用来检查活动是否结束的线程
		System.out.println("抢票活动开始");
		List<Buyer> buyers=new Buyer().initBuyers(setBuyerNum);
		System.out.println(buyers.size()+"个买家创建完成");
		Seller se=new Seller();
		se.start();
		System.out.println("商家启动");
		System.out.println("启动买家群");
		for(int i=0;i<buyers.size();i++){
			buyers.get(i).start();
		}
		
	} 
	
	static class Guard extends Thread{              //守护线程,用于检查活动是否结束并打印结果
		
		public void run() {
			super.run();
			this.isEnd();
		}
		
		public void isEnd(){
			while(listPaid.size()<totalTicketNum){  //检查票是否已经全部卖完并付款
					try {
						sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
			};
			end=true;  //活动结束
			System.out.println("活动结束了");
			System.out.println("商家收款"+sellerBalance+"元");
			System.out.println("卖出票"+listPaid.size()+"张");
			
			System.out.println("打印未排序的listPait");
			for(int i=0;i<=listPaid.size()-1;i++){              //打印未排序的listPaid
					System.out.println(listPaid.get(i));
			}

			System.out.println("依照票号打印listPaid");
			Collections.sort(listPaid,(a,b)->(((Integer)a.getTicketNumber()).compareTo((Integer)b.getTicketNumber())));
			for(int i=0;i<=listPaid.size()-1;i++){              //依照票号打印每张票的票号和购买人
					System.out.println(listPaid.get(i));
			}
			
			System.out.println("依照购票者姓名打印listPaid");
			Collections.sort(listPaid,(a,b)->((a.getBuyerName()).compareTo(b.getBuyerName())));
			for(int i=0;i<=listPaid.size()-1;i++){              //依照购票者打印每张票的票号和购买人
					System.out.println(listPaid.get(i));
			}
			
			
			
			
			int test=0;                                         //检查是否有相同票号或相同购买人, 如果有test就不为0       
			for(int i=0;i<listPaid.size()-1;i++){               
				for(int j=i+1;j<listPaid.size();j++){
					if(listPaid.get(i).buyerName.equals(listPaid.get(j).buyerName)){
							test++;}
					if(listPaid.get(i).ticketNumber==(listPaid.get(j).ticketNumber)){
						test++;}
					}
			}
			if(test==0){
				System.out.println("结果复核正确");
				}else{
					System.out.println("出错了");
				}	
			System.out.println("商家收款"+sellerBalance+"元");
			System.out.println("卖出票"+listPaid.size()+"张");
		}
	}
	
	//票务类
	 static class Ticket{
		  int ticketNumber;
		  String buyerName;
		  
		public Ticket(int ticketNumber,String buyerName) {
			super();
			this.ticketNumber = ticketNumber;
			this.buyerName = buyerName;
		}

		@Override
		public String toString() {
			return ticketNumber + "\t" + buyerName;
		}
		
		public int getTicketNumber() {
			return ticketNumber;
		}

		public void setBuyerName(String buyerName) {
			this.buyerName = buyerName;
		}

		public void setTicketNumber(int ticketNumber) {
			this.ticketNumber = ticketNumber;
		}

		public String getBuyerName() {
			return buyerName;
		}
		
	}
	 
	 	//买家类
	static class Buyer extends Thread{
		String name;  
		int boughtTicketNumber; //票号
		
		//买家构造方法
		public Buyer(String name,int boughtTicketNumber) {
			super();
			this.name = name;
			this.boughtTicketNumber = boughtTicketNumber;
		}

		public Buyer() {
			// TODO Auto-generated constructor stub
		}

		public int getBoughtTicketNumber() {
			return boughtTicketNumber;
		}

		public void setBoughtTicketNumber(int boughtTicketNumber) {
			this.boughtTicketNumber = boughtTicketNumber;
		}

		//创建买家
		public List<Buyer> initBuyers(int n){
			List<Buyer> list=new Vector<>();
			Buyer newBuyer;
			 
			for(int i=0;i<n;i++){
				String buyerName="买家姓名编号"+(i+1);
					newBuyer=new Buyer(buyerName,0);
					list.add(newBuyer);
			}
			return list;
		} 
		
		//重写买家run方法
		@Override
		public void run() {
			
			while (end==false) {
				    Ticket tempTicket = null;            //用来存放锁定的票
					synchronized ("a") {
					 if(list.size()>0){
						 	if(this.boughtTicketNumber==0){          //如果付款了boughtTicketNumber就不是0了,就不能买票
						        tempTicket=list.remove(0);
						        System.out.println(this.name+"锁定票号:"+tempTicket.ticketNumber);
						 	}
						}else{
							
							synchronized ("b") {         //通知卖家准备票
								"b".notifyAll();
							}
							try {								
								"a".wait();             //买家进入等待模式
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}								
					} //end of "a"
					
				if (tempTicket!=null) {                        
					int decide=(int) (Math.random()*10);   //决定是否付款,decide>5付款
					
					if(decide>5){
						synchronized("d"){
								sellerBalance += ticketPrice;       //付款环节用synchronized修饰,用volatile修饰不能保证安全
							}					
						tempTicket.setBuyerName(this.name);
						listPaid.add(tempTicket); //将tempTicket传到listPaid,如果是ArrayList要用synchronized上锁				
						this.setBoughtTicketNumber(tempTicket.ticketNumber);
						System.out.println(this.name + "付款了,上传票号"+tempTicket.ticketNumber+"上传姓名"+tempTicket.buyerName);	
					}else{
						list.add(tempTicket);   //将tempTicket传到list,如果是ArrayList LinkedList要上锁
						System.out.println(this.name+"放弃购买"+tempTicket.ticketNumber);	
					}
				}
			}//end of while
		}//end of run	
	} //end of class buyer
			
	//商家类
	static class Seller extends Thread{
		public Seller() {
			super();
		}
		
		//重写商家run()
		@Override
		public void run() {
			super.run();
			int ticketNum=0;
			while (end==false) {
				synchronized("a") {
					System.out.println("开始准备下一批票了");
					int thisBatchNum;//本次出票数		
					if ((totalTicketNum - ticketNum) >= batchNum) {
							thisBatchNum = batchNum;
						} else {
							thisBatchNum = totalTicketNum - ticketNum;
						}
					if(thisBatchNum>0){
						System.out.println("总票数"+totalTicketNum);
						System.out.println("已出票"+ticketNum);
						System.out.println("本次出票"+thisBatchNum);
					} else{
						synchronized("c"){ //如果全部的票都出了,商家就永远的等待了,没有notify("c")的语句
							try {
								"c".wait();
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
					
					for (int i = 0; i < thisBatchNum; i++) {            //票号1-1000,购买者姓名为空值
						list.add(new Ticket(ticketNum+1,""));
						ticketNum++;
					}
					System.out.println("准备开始抢票啦");
					"a".notifyAll();
				} //end of "a"
				synchronized ("b") {     //商家出完一轮票后进入等待
					System.out.println("没票了再叫我");
					
					try {
						"b".wait();
						System.out.println("商家起床");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} //end of "b"
			
			}//end of while(true)
		}//end of run
	} //end of class seller

} //end of class


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值