售票系统

设计一个简单的卖票模型,就是有个卖票系统,设定好窗口数和总票数,开始卖票,各个窗口共享总票数,卖完为止。

下面是代码:

public class SellTicketSystem{
    private int totalTickets;
    private int i;

    public void setTotalTickets(int totalTickets) {
        this.totalTickets = totalTickets;
    }

    public void startSellTicket() {
        Thread firstWindow = new Thread(new Runnable() {
            @Override
            public void run() {
                while (totalTickets>0){
                    totalTickets--;
                    System.out.println("firstWindow sell " + (++i)+"th ticket");
                }
            }
        },"firstWindow");

        Thread secondWindow = new Thread(new Runnable() {
            @Override
            public void run() {
                while (totalTickets>0){
                    totalTickets--;
                    System.out.println("secondWindow sell " + (++i)+"th ticket");
                }
            }
        }, "secondWindow");

        Thread thirdWindow = new Thread(new Runnable() {
            @Override
            public void run() {
                while (totalTickets>0){
                    totalTickets--;
                    System.out.println("thirdWindow sell " + (++i)+"th ticket");
                }
            }
        }, "thirdWindow");

        firstWindow.start();
        secondWindow.start();
        thirdWindow.start();
    }

}
public class Main {

    public static void main(String[] args) {
        /**
         * 多线程售票共享变量测试
         * */
        SellTicketSystem sellTicketSystem = new SellTicketSystem();
        sellTicketSystem.setTotalTickets(20);
        sellTicketSystem.startSellTicket();
    }
}
G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=59509:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
thirdWindow sell 3th ticket
secondWindow sell 2th ticket
firstWindow sell 1th ticket
secondWindow sell 5th ticket
thirdWindow sell 4th ticket
secondWindow sell 7th ticket
firstWindow sell 6th ticket
secondWindow sell 9th ticket
thirdWindow sell 8th ticket
secondWindow sell 11th ticket
firstWindow sell 10th ticket
secondWindow sell 13th ticket
thirdWindow sell 12th ticket
secondWindow sell 15th ticket
firstWindow sell 14th ticket
secondWindow sell 17th ticket
thirdWindow sell 16th ticket
secondWindow sell 19th ticket
firstWindow sell 18th ticket
thirdWindow sell 20th ticket

Process finished with exit code 0

可以看到基本实现了开头说的功能,设置了3个窗口(3个线程),设置了总票数20张,各个窗口没有卖重复的票。

但是有个不足的地方,三个窗口卖票的顺序是乱的,譬如三号窗口卖出了第三张票,二号窗口卖出了第二张票,才到一号窗口卖出第一张票,这是不合常理的。应该是先卖出第一张票,再卖出第二张票,再卖出第三张票,这样才是正确的。

下面我们进行改进,让系统按顺序出票。顺便看看之前写的程序还有什么可以优化的地方。

我们可以看到三个线程执行的方法都差不多,可以考虑抽取出来。

    while (totalTickets>0){
        totalTickets--;
        System.out.println("thirdWindow sell " + (++i)+"th ticket");
    }

新建一个TicketWindow类,实现Runnable接口。

public class TicketWindow implements Runnable {

    public int totalTickets;
    public int i=0;
    
    public void setTotalTickets(int totalTickets) {
        this.totalTickets = totalTickets;
    }

    /**
     * 把原有的三个线程的方法抽出来,写到这。
     * */
    @Override
    public void run() {
        while (totalTickets>0){
            totalTickets--;
            System.out.println(Thread.currentThread().getName() + " sell" + (++i) + "th ticket");
        }
    }
}
public class SellTicketSystem{
    private int totalTickets;
    
    public void setTotalTickets(int totalTickets) {
        this.totalTickets = totalTickets;
    }
    public void startSellTicket() {
        //创建一个Runnable
        TicketWindow ticketWindow = new TicketWindow();
        ticketWindow.setTotalTickets(totalTickets);

        //三个线程共用一个Runnable
        Thread firstWindow = new Thread(ticketWindow, "firstWindow");
        Thread secondWindow = new Thread(ticketWindow, "secondWindow");
        Thread thirdWindow = new Thread(ticketWindow, "thirdWindow");
        
        firstWindow.start();
        secondWindow.start();
        thirdWindow.start();
    }
    public static void main(String[] args) {
       	/**
         * 多线程售票共享变量测试
         * */
        SellTicketSystem sellTicketSystem = new SellTicketSystem();
        sellTicketSystem.setTotalTickets(20);
        sellTicketSystem.startSellTicket();
    }
G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=64681:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
secondWindow sell 2th ticket
thirdWindow sell 3th ticket
firstWindow sell 1th ticket
thirdWindow sell 5th ticket
secondWindow sell 4th ticket
thirdWindow sell 7th ticket
firstWindow sell 6th ticket
thirdWindow sell 9th ticket
secondWindow sell 8th ticket
thirdWindow sell 11th ticket
firstWindow sell 10th ticket
thirdWindow sell 13th ticket
secondWindow sell 12th ticket
thirdWindow sell 15th ticket
firstWindow sell 14th ticket
thirdWindow sell 17th ticket
secondWindow sell 16th ticket
thirdWindow sell 19th ticket
firstWindow sell 18th ticket
secondWindow sell 20th ticket

Process finished with exit code 0

没有重复的票出现,方法抽取成功。接下来实现顺序卖票功能。对上面写好的TicketWindow类中实现的run方法改造。

    /**
     * 把原有的三个线程的方法抽出来,写到这。
     * */
    @Override
    public void run() {
        while (totalTickets>0){
            totalTickets--;
            System.out.println(Thread.currentThread().getName() + " sell" + (++i) + "th ticket");
        }
    }

上面的方法改写成下面这样

    /**
     * 把原有的三个线程的方法抽出来,写到这。
     * */
    @Override
    public void run() {
        while (totalTickets > 0) {
            totalTickets--;
            synchronized (this){
                System.out.println(Thread.currentThread().getName() + " sell " + (++i) + "th ticket");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

运行结果如下

G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=51499:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
firstWindow sell 1th ticket
secondWindow sell 2th ticket
secondWindow sell 3th ticket
secondWindow sell 4th ticket
thirdWindow sell 5th ticket
thirdWindow sell 6th ticket
thirdWindow sell 7th ticket
thirdWindow sell 8th ticket
thirdWindow sell 9th ticket
thirdWindow sell 10th ticket
thirdWindow sell 11th ticket
thirdWindow sell 12th ticket
thirdWindow sell 13th ticket
thirdWindow sell 14th ticket
thirdWindow sell 15th ticket
secondWindow sell 16th ticket
secondWindow sell 17th ticket
secondWindow sell 18th ticket
firstWindow sell 19th ticket
thirdWindow sell 20th ticket

Process finished with exit code 0

实现了顺序卖票。在实现顺序卖票的过程中,踩过几个坑,也写下来提醒自己。

以下是踩坑环节

第一个是把synchronized加在了while之前。

    /**
     * 把原有的三个线程的方法抽出来,写到这。
     */
    @Override
    public void run() {
        synchronized (this){
            while (totalTickets > 0) {
                totalTickets--;
                System.out.println(Thread.currentThread().getName() + " sell " + (++i) + "th ticket");
            }
        }
    }

这样做的后果就是,哪一个线程拿到锁,就由哪一个线程把票卖完,其他线程没得卖。运行结果如下

G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=57339:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
firstWindow sell 1th ticket
firstWindow sell 2th ticket
firstWindow sell 3th ticket
firstWindow sell 4th ticket
firstWindow sell 5th ticket
firstWindow sell 6th ticket
firstWindow sell 7th ticket
firstWindow sell 8th ticket
firstWindow sell 9th ticket
firstWindow sell 10th ticket
firstWindow sell 11th ticket
firstWindow sell 12th ticket
firstWindow sell 13th ticket
firstWindow sell 14th ticket
firstWindow sell 15th ticket
firstWindow sell 16th ticket
firstWindow sell 17th ticket
firstWindow sell 18th ticket
firstWindow sell 19th ticket
firstWindow sell 20th ticket

Process finished with exit code 0
G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=55455:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
secondWindow sell 1th ticket
secondWindow sell 2th ticket
secondWindow sell 3th ticket
secondWindow sell 4th ticket
secondWindow sell 5th ticket
secondWindow sell 6th ticket
secondWindow sell 7th ticket
secondWindow sell 8th ticket
secondWindow sell 9th ticket
secondWindow sell 10th ticket
secondWindow sell 11th ticket
secondWindow sell 12th ticket
secondWindow sell 13th ticket
secondWindow sell 14th ticket
secondWindow sell 15th ticket
secondWindow sell 16th ticket
secondWindow sell 17th ticket
secondWindow sell 18th ticket
secondWindow sell 19th ticket
secondWindow sell 20th ticket

Process finished with exit code 0
G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=49543:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
thirdWindow sell 1th ticket
thirdWindow sell 2th ticket
thirdWindow sell 3th ticket
thirdWindow sell 4th ticket
thirdWindow sell 5th ticket
thirdWindow sell 6th ticket
thirdWindow sell 7th ticket
thirdWindow sell 8th ticket
thirdWindow sell 9th ticket
thirdWindow sell 10th ticket
thirdWindow sell 11th ticket
thirdWindow sell 12th ticket
thirdWindow sell 13th ticket
thirdWindow sell 14th ticket
thirdWindow sell 15th ticket
thirdWindow sell 16th ticket
thirdWindow sell 17th ticket
thirdWindow sell 18th ticket
thirdWindow sell 19th ticket
thirdWindow sell 20th ticket

Process finished with exit code 0

这是运行了N次的结果,拿出3次比较好辨认的结果出来比较,可以看到3次中的任意一次都是一条线程跑到底。有点类似赢家通吃。

第二个踩过的坑是把synchronized加在while之后,totalTickets之前。

    /**
     * 把原有的三个线程的方法抽出来,写到这。
     */
    @Override
    public void run() {
        while (totalTickets > 0) {
            synchronized (this) {
                totalTickets--;
                System.out.println(Thread.currentThread().getName() + " sell " + (++i) + "th ticket");
            }
        }
    }

这样做的后果是,永远会多出两张本不存在的票,运行结果如下。

G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=52341:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
firstWindow sell 1th ticket
firstWindow sell 2th ticket
firstWindow sell 3th ticket
firstWindow sell 4th ticket
firstWindow sell 5th ticket
firstWindow sell 6th ticket
firstWindow sell 7th ticket
firstWindow sell 8th ticket
firstWindow sell 9th ticket
firstWindow sell 10th ticket
firstWindow sell 11th ticket
firstWindow sell 12th ticket
firstWindow sell 13th ticket
firstWindow sell 14th ticket
firstWindow sell 15th ticket
firstWindow sell 16th ticket
firstWindow sell 17th ticket
firstWindow sell 18th ticket
firstWindow sell 19th ticket
firstWindow sell 20th ticket
thirdWindow sell 21th ticket
secondWindow sell 22th ticket

Process finished with exit code 0

从结果可以看出,一共20张票,第一个窗口全卖完了,按道理来说卖完了应该没票了,但是后面第二个窗口和第三个窗口居然还卖出了第21张,第22张票。不可思议。

看了看run方法中的代码,猜测是线程在一开始totalTickets>0判断时全都通过,然后进入了while的循环代码块,之后被synchronized(this) 拦截。

一番竞争之后,某一个线程通过了synchronized(this) 的拦截,进入了卖票环节,然后把票卖完了,退出代码块。剩下的线程就又在synchronized(this) 这里竞争,竞争通过的又进去,但是因为totalTickets在这里已经是<=0了,所以只卖了一次票就退出了代码块,然后剩下的代码又竞争,竞争后也是卖了一次票就退出了代码块。

我们改写下run方法,方便测试。

    /**
     * 把原有的三个线程的方法抽出来,写到这。
     */
    @Override
    public void run() {
        while (totalTickets > 0) {
            System.out.println(Thread.currentThread().getName());
            synchronized (this) {
                totalTickets--;
                System.out.println(Thread.currentThread().getName() + " sell " + (++i) + "th ticket");
            }
        }
    }
G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=49333:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
firstWindow
thirdWindow
secondWindow
firstWindow sell 1th ticket
firstWindow
firstWindow sell 2th ticket
firstWindow
thirdWindow sell 3th ticket
thirdWindow
thirdWindow sell 4th ticket
thirdWindow
thirdWindow sell 5th ticket
thirdWindow
thirdWindow sell 6th ticket
thirdWindow
thirdWindow sell 7th ticket
thirdWindow
thirdWindow sell 8th ticket
thirdWindow
thirdWindow sell 9th ticket
thirdWindow
thirdWindow sell 10th ticket
thirdWindow
secondWindow sell 11th ticket
secondWindow
thirdWindow sell 12th ticket
thirdWindow
firstWindow sell 13th ticket
firstWindow
thirdWindow sell 14th ticket
thirdWindow
secondWindow sell 15th ticket
secondWindow
thirdWindow sell 16th ticket
thirdWindow
firstWindow sell 17th ticket
firstWindow
thirdWindow sell 18th ticket
thirdWindow
secondWindow sell 19th ticket
secondWindow
secondWindow sell 20th ticket
thirdWindow sell 21th ticket
firstWindow sell 22th ticket

Process finished with exit code 0

结果跟我们之前想的有些许偏差,

“一番竞争之后,某一个线程通过了synchronized(this)的拦截,进入了卖票环节,然后把票卖完了,退出代码块。剩下的线程就又在synchronized(this)这里竞争,竞争通过的又进去,但是因为totalTickets在这里已经是<=0了,所以只卖了一次票就退出了代码块,然后剩下的代码又竞争,竞争后也是卖了一次票就退出了代码块。”

这是我们刚才说的,偏差部分已上色,从运行结果可以看出,通过while判断的线程确实是堆积在了whiletotalTickets之间,

但是之后并不是如我们之前所想的由一个线程把票全卖完了,剩余的线程再进行争夺。而是某一个线程通过竞争进入synchronized(this) 代码块后,卖出一张票,然后退出synchronized(this) 代码块,继续参与竞争,通过竞争进入synchronized(this) 代码块的线程又是卖了一张票后,退出synchronized(this) 代码块重新与别的线程竞争,周而复始,一直到totalTickets<=0

当某个线程把最后一张票卖出,则while循环停止的条件达成,这个线程就跳出了while循环,剩余的线程继续在synchronized(this) 代码块竞争,然后进入代码块内卖出一张不存在的票,然后退出循环。

由此得出,多卖出几张票,取决于有几个线程,3个线程,就会多卖出3-1=2张票。N个线程,就会多卖出N-1张票。

下面测试一下

public void startSellTicket() {
        //创建一个Runnable
        TicketWindow ticketWindow = new TicketWindow();
        ticketWindow.setTotalTickets(totalTickets);

        //四个线程共用一个Runnable
        Thread firstWindow = new Thread(ticketWindow, "firstWindow");
        Thread secondWindow = new Thread(ticketWindow, "secondWindow");
        Thread thirdWindow = new Thread(ticketWindow, "thirdWindow");
        Thread fourthWindow = new Thread(ticketWindow, "fourthWindow");

        firstWindow.start();
        secondWindow.start();
        thirdWindow.start();
        fourthWindow.start();
}
    /**
     * 把原有的四个线程的方法抽出来,写到这。
     */
    @Override
    public void run() {
        while (totalTickets > 0) {
            System.out.println(Thread.currentThread().getName());
            synchronized (this) {
                totalTickets--;
                System.out.println(Thread.currentThread().getName() + " sell " + (++i) + "th ticket");
            }
        }
        System.out.println(Thread.currentThread().getName()+" out of while loop");
    }

我们多加一个线程,然后在while循环外加上println语句。运行结果如下

G:\jdk\bin\java.exe -javaagent:G:\IDEA\lib\idea_rt.jar=61249:G:\IDEA\bin -Dfile.encoding=UTF-8 -classpath G:\GITHUB\DesignPattern\target\classes Main
firstWindow
thirdWindow
fourthWindow
secondWindow
firstWindow sell 1th ticket
firstWindow
firstWindow sell 2th ticket
firstWindow
firstWindow sell 3th ticket
firstWindow
firstWindow sell 4th ticket
firstWindow
firstWindow sell 5th ticket
firstWindow
firstWindow sell 6th ticket
firstWindow
firstWindow sell 7th ticket
firstWindow
secondWindow sell 8th ticket
secondWindow
fourthWindow sell 9th ticket
fourthWindow
fourthWindow sell 10th ticket
fourthWindow
thirdWindow sell 11th ticket
thirdWindow
fourthWindow sell 12th ticket
fourthWindow
secondWindow sell 13th ticket
secondWindow
firstWindow sell 14th ticket
firstWindow
secondWindow sell 15th ticket
secondWindow
fourthWindow sell 16th ticket
fourthWindow
thirdWindow sell 17th ticket
thirdWindow
thirdWindow sell 18th ticket
thirdWindow
thirdWindow sell 19th ticket
thirdWindow
fourthWindow sell 20th ticket
secondWindow sell 21th ticket
firstWindow sell 22th ticket
thirdWindow sell 23th ticket
fourthWindow out of while loop
secondWindow out of while loop
thirdWindow out of while loop
firstWindow out of while loop

Process finished with exit code 0

从结果看出,完全符合我们的猜想。
至此踩过的坑也全都分析完毕,得出结论,synchronized 的范围应该尽可能的小,这样才能防止其它因素的干扰。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
#这只是一个半成品,只是实现了,一个完整的订票过程,对于,如果刷票,自己研究 #简单过程 # 第一、getRandAndCookie() 获得cook 和一个随机数用于登录 # 第二、getEnterRandCode() 得到登录时的识别码 # 第三、setuseandpassword(randcode,use,password) 发送随机数、识别码和用户及密码。由于随机数只在内部使用,所以定义成了全局变量, # 第四、GetTrainList() 得到所有车站列表,'@bjb|北京北|VAP|beijingbei|bjb|0' 其中有中文、拼音、拼音缩写、所一个ID(唯一),其主要是可以,通过上面的列表,找到它的唯一ID,TranCityToId('南昌') # 第五、GetTrainNumList(date,fromstationid,tostationid,starttime) 得到哪到哪的所在车次,消息格式如下,其中所以,一下车次的的ID:"id":"650000K1060I" # {"end_station_name":"北京西","end_time":"16:18","id":"650000K1060I","start_station_name":"深圳","start_time":"10:54","value":"K106"} # 通过ChangeToTrainNumId('K106')得到车次ID # 第六、QueryTrain(fromstationid,tostationid,date,stationNum,starttime) 就是点击查询按键,得到是否有能预订,格式如下 #       南昌         20:12,    北京西        07:38,11:26,--,--,--,--,10,有,有,--,有,有,--,<a name='btn130_2' class='btn130_2' # 通过choiceSubmitNum(stationNum,trainsubmitinfo)提取出getSelected()消息 # 第七、submitRequest(choiceSubmitNum(stationNum,trainsubmitinfo),date,starttime) 就是点击预订按钮 # 第八、getrandCheckCode()得到提交订单的识别码 # 第十、CheckInMyTicket(info,randcode,peoples)点击提交,如果成功的话,就会返回{"errMsg":"Y"} # 出于,网络是UTF8格式,所以,必须# -*- coding: utf-8 -*-,(当然,自己转换也是可以的) # 出于这一个控制台信息,所以,识别码的图片在脚本同一目录 #得到头信息

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值