浅析体育赛事售票系统错票问题的对策研究

2022-01-15 07:07
体育科技文献通报 2022年1期
关键词:线程门票代码

戈 俊

1 研究目的

现实生活中,体育赛事的售票一般是并发执行,要求多窗口同时进行售票任务。并且要保证售票顺利进行,不可以出现错票现象。如何通过多线程同步技术解决错票问题,是本研究的主要目的。

2 研究方法

2.1 文献资料法

根据研究内容和研究目的,查阅了近年来有关多线程技术等方面的专著、期刊、论文和资料,并对资料进行整理分析、筛选、归纳、概括。为写作提供依据,为后续研究提供了充足的理论支持。

2.2 实验法

通过Eclipse集成开发软件,建立JavaSE开源项目,通过创建包、接口、类、配置文件等方法,进行项目开发的基本配置,通过WindowBuilder插件,进行GUI可视化组件开发,使用多线程技术开发体育赛事售票系统,结合错票问题提出解决方案。

3 研究结果与分析

3.1 多线程技术的理论分析

3.1.1 进程与线程的关系分析

进程顾名思义是正在进行中的程序。当我们在执行一个程序时,程序启动后会在内存中开辟空间,这个被开辟的空间就是进程,进程是一个应用程序对应内存中的一片空间,等待程序运行完毕后,会将此片空间释放掉,硬盘是持久化存储,而内存是程序运行时临时存储的。线程是任意进程在内存中的执行路径,当进程开辟多个执行路径并同时操作多部分代码时,即开启多个线程任务。

3.1.2 多线程创建方式分析

创建线程目的是为了开启一条执行路径去运行指定的代码,和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务[1]。Java中Thread类用于描述线程,线程是需要任务,这个任务就通过Thread类中的run方法来体现,run方法就是封装自定义线程运行任务的函数[1]。run方法中定义的就是线程要运行的任务代码,开启线程是为了运行指定代码,所以只有继承Thread类并复写run方法,将运行的代码定义在run方法中即可[1]。

Oracle公司在定义Thread类时,先定义了一个私有的Runnable接口引用的全局变量[2]。定义一个带参构造函数,而构造函数的参数就是Runnable接口。通过参数传递,将局部变量的参数传递给全局变量。同时在Thread类的run方法中定义了如果全局变量不为空的话。就运行实现Runnable接口子类对象中的run方法[2]。Runnable接口的出现,仅仅是将线程任务进行了对象的封装。

3.1.3 多线程运行时内存管理分析

只要开启一条执行路径,栈内存中就随即存在了一条单独的执行路径,当程序运行时调用到了主方法,主线程即可被创建出来,主线程在顺序执行的过程中,会执行到继承自线程Thread对象的子类对象,通过关键字new线程对象时,即创建了新的线程。当主线程读到start方法时,在栈内存中开启了新的线程路径,在主线程中执行的内容是main方法中的内容,而新的线程路径中执行的则是继承自Thread线程对象被子类覆盖的run方法中的内容。每条线程在栈内存中都被分配了独立的空间。同时,主线程中调用的方法就在主线程中压栈弹栈,而run方法中调用的方法将在新开辟的路径中压栈弹栈,新开启的线程都是由Thread-加上数字来命名的。也就是说每个run方法中都有自己所属的栈区,run方法中定义的局部变量也都在各自的栈区run方法内[3]。一旦run方法内的所有任务执行完,该线程的run方法弹栈,该线程所分配的执行空间被释放。多线程程序运行时,即便主函数先运行完毕弹栈后,该程序的其他正在运行的线程依然存在,保证着程序的正常运行。

3.1.4 多线程执行的状态分析

当应用程序在执行时,CPU在多个执行线程中做着高速切换,这个切换是随机的。一旦线程处于运行状态时,CPU在对其进行处理将分两种状态:正在被处理表明该线程具备CPU的执行资格和执行权;在处理队列中排队表明该线程具备着CPU的执行资格[4]。当线程运行时执行到sleep方法或wait方法时就进入冻结状态,这是线程在释放执行权的同时释放了执行资格的过程。如果想让冻结的线程恢复到运行状态,可以等待设置的休眠时间times up或使用notify唤醒线程。当被冻结的线程被唤醒后将进入两种状态(运行状态/临时阻塞状态),处于临时阻塞状态的线程具备着执行资格,但是不具备执行权[5]。此时就看CPU有没有切到该线程上。当一个线程被创建后,通过start方法开启并运行该线程,如果线程任务结束后那就是消亡状态,通过stop方法也可以结束线程使线程进入消亡状态。

3.2 体育赛事售票系统错票问题产生及解决方式分析

3.2.1 建立售票对象时继承Thread出现严重错票现象

首先创建票的数量作为该类的全局变量,此时把票定义为100张。通过循环语句,可以执行售票方法。票务将进行递减操作,售完一张在票务的总数上递减一张。由于门票对象已经继承了线程对象,那么该门票就是线程对象,可以创建多个售票窗口的同时调用线程类中的run方法执行售票程序。此时系统将出现一个问题,4个线程分别都售出了100张门票,共计售出400张门票,出现了严重的错票现象。主要原因是每个对象创建,堆内存中都有个引用变量门票数ticketNumber,默认初始化为0,显示初始化为100。但在调用过程中,每个线程中间都有自己的run方法,而执行的时候每个run都有自己的对象所属。所以引用变量ticketNumber在各自对象中进行操作,于是4个线程操作了4个ticketNumber。

3.2.2 通过实现Runnable接口临时解决错票问题

出现错票现象以后,可以换一种思路,通过实现Runnable接口进行多线程的售票。这样将符合实现Runnable接口的优势,将线程任务进行独立的封装[2]。同时开启4个线程将售票任务作为参数传递给4个线程,4个线程将共同操作同一个任务,这样就不会出现错票现象。

很明显,4个线程同时在售卖100张票,此时并没有出现错票现象。但是这种情况真的不会出现错票现象吗?如果4个线程共同售卖同100张门票时,其中有一个线程临时处于了等待状态并没有及时售出门票。当该线程处于等待状态时,线程任务的执行权将被切换到其他线程身上。再次将执行权切回到本线程身上时,就有可能出现错票现象。这种现象,很容易出现在最后几张票的售卖过程中,往往会出现负数票现象。我们可以通过让线程休眠若干毫秒来模拟实现错票现象。如图1所示。

图1 让线程短暂休眠后的错票现象

3.2.3 实现Runnable接口后错票现象依然存在的原因分析

通过让线程休眠若干毫秒来模拟线程安全问题时,可以假设当门票已经售卖到最后一张,那么门票数ticketNumber就为1。而4个线程在争夺执行权的同时都进入了售票循环系统中,判断条件是ticketNumber只要大于0就可以继续进入If条件语句的执行体内,ticketNumber目前等于1已经满足If判断语句的条件,任何一个线程进入判断语句的执行体内,由于先执行到之前为了模拟票务系统出现错误的休眠语句,让该线程在if执行语句的执行体内休眠1000毫秒。与此同时,if执行体内的执行语句并未被执行,于是门票数ticketNumber并未得到改变。而当该线程处于休眠状态以后,释放了执行权。其他线程获取了执行权以后,将直接进入if语句的条件判断,由于ticketNumber仍然是大于0,所以该线程仍然可以进入if的执行体内。以此类推,每个线程进入if判断语句的执行体内,都会按照我们事先设定好的让线程休眠1000毫秒,让线程释放执行权。而当每个线程依次恢复执行状态时,都会进行ticketNumber减减的动作,这时就会出现错票现象。出现负票,因为ticketNumber分别被4个线程从1减到0,从0减到-1,从-1减到-2。

3.2.4 通过同步代码块或同步函数解决错票问题

当一个线程读到synchronized同步代码块时,该线程会判断并检查同步代码块中的参数锁对象是否存在[6]。如果存在将携带该锁进入同步内继续执行;如果不存在即便已获取执行权的线程也无法进入同步内,因为无法获取并持有同步锁对象。持有同步代码块参数对象的线程,即使在同步内休眠释放执行权,其他线程也无法持有对象锁,将无法进入同步内执行相应代码,只有当该线程执行完同步内所有代码,出同步代码块时,该线程会释放同步锁对象。这样其他线程才有可能持有该同步锁进入同步内,这样就避免了出现线程安全的问题。如图2所示。

图2 同步代码块将操作共享数据的多条代码进行封装解决错票问题

4 结论与建议

4.1 结论

体育赛事的门票售卖多为并发执行,要求多窗口同时进行售票任务,在技术选型上倾向于多线程技术,由于线程任务执行时会偶发临时阻塞状态,待运行状态恢复时极易触发错票事故,主要问题在于一个线程在操作多条作为共享数据的体育赛事门票代码的同时,其他线程也有可能在争夺执行权的情况下参与运算。

4.2 建议

将作为共享数据的体育赛事门票代码封装打包成一个整体并加上锁,当一个线程拿到锁进入封装体内售卖门票时,其他线程无法获取该封装体的锁,于是无法参与同时售卖,此举有效地避免了错票事故的发生。只有当售票线程结束售票任务离开封装体后,将锁移交其他线程,这样其他线程才有可能效仿前者执行售票任务。

猜你喜欢
线程门票代码
5G终端模拟系统随机接入过程的设计与实现
我的专属门票
实时操作系统RT⁃Thread启动流程剖析
魔方小区
实时操作系统mbedOS 互斥量调度机制剖析
北京“门票达人”办展览
乡村旅游不再是“门票+凉皮”
神秘的代码
一周机构净增(减)仓股前20名
重要股东二级市场增、减持明细