zoukankan      html  css  js  c++  java
  • 线程安全问题经典案例---卖票

      在入门多线程的时候,看到过不少的案例,其中卖票案例尤为经典,在这里自己也记录一下,同时加深对于线程安全的理解:

    案例场景
    1. 情景一:

       现在有一个电影院,马上要上映电影《战狼5》,电影院只售票100张,全部通过一个窗口卖出去,那么此种情况则不会出现电影票超卖或者其他任何情况。
    在这里插入图片描述

    1. 情景二:

      现在电影院装修升级了,新增了两个卖票窗口,1号窗口卖1-30号票,2号窗口卖31-60号票,3号窗口卖61-100号票。相当于三个窗口各卖各的,互不干扰。所以此种情况也不会产生任何问题。
    在这里插入图片描述

    1. 情景三:

       现在卖票的方式发生了改变,3个窗口都卖1-100号票,那现在就可能会出现一些情况,窗口1在卖100号票的时候(正在和顾客沟通中),3号窗口一看该卖100号票了(最后一张),啪!直接将100号票给了顾客。此时窗口1与顾客沟通确认无误之后,打算出票了,结果一看,100号票么得了~顾客当场开始砸场子……
    在这里插入图片描述
      情景三中,我们三个窗口相当于是三个线程,共同去卖那100张票相当于就是共同访问同一份共享资源。在这种条件下,就可能出现我们所说的线程安全问题(线程安全发生的条件:多线程共同访问同一份共享资源

    情景三代码实现
    public class RunnableImpl implements Runnable {
    
    	//给一个共享资源
    	private int ticket = 100;
    
    	//指定线程所要执行的任务(卖票)
    	@Override
    	public void run() {
    		while (true) {
    			if (ticket > 0) {
    				System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张电影票");
    				try {
    					//为了卖票出错的情况更加明显,我们让线程等待100ms
    					Thread.sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				--ticket;
    			} else {
    				System.out.println("票卖完了");
    				return;
    			}
    		}
    	}
    }
    
    public class FileTest {
    
    	public static void main(String[] args) {
    
    		//思考:为什么这里只new 了一个Runnable的对象传到了三个线程种?
    		RunnableImpl runnable = new RunnableImpl();
    
    		//new 三个线程出来模三个窗口,为了符合情景,我们为线程设置一个线程名
    		Thread thread1 = new Thread(runnable);
    		thread1.setName("窗口1");
    
    		Thread thread2 = new Thread(runnable);
    		thread2.setName("窗口2");
    
    		Thread thread3 = new Thread(runnable);
    		thread3.setName("窗口3");
    
    		thread1.start();
    		thread2.start();
    		thread3.start();
    
    	}
    
    }
    

     在上面的代码中,我们有一个思考:为什么这里只new 了一个Runnable的对象传到了三个线程种?

      因为我们需要让三个窗口来共同卖那100张票,所以他们访问的是同一个共享资源,如果这里new 了3个Runnable对象并分别放到3个Thread对象中(3个不同的窗口中),那三个窗口访问就不是同一份资源了,既然不是同一份资源,也就不满足线程安全问题产生的条件。自然不会产生线程安全问题。

    启动之后,我们发现三个窗口直接把票卖重了,都在卖100号票,这不是搞事情嘛……
    在这里插入图片描述
    电影院为了避免出现上面这种卖票混乱的情况,决定对售票系统进行优化:

    安全卖票代码实现
    public class RunnableImpl implements Runnable {
    
    	//给一个共享资源
    	private int ticket = 100;
    
    	//指定线程所要执行的任务(卖票)
    	@Override
    	public void run() {
    		while (true) {
    			//这里我们使用synchronized关键字来锁定卖票系统
    			synchronized (this) {
    				if (ticket > 0) {
    					System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张电影票");
    					try {
    						//为了卖票出错的情况更加明显,我们让线程等待100ms
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					--ticket;
    				} else {
    					System.out.println("票卖完了");
    					return;
    				}
    			}
    		}
    	}
    }
    
    public class FileTest {
    
    	public static void main(String[] args) {
    
    		//思考:为什么这里只new 了一个Runnable的对象传到了三个线程种?
    		RunnableImpl runnable = new RunnableImpl();
    
    		//new 三个线程出来模三个窗口,为了符合情景,我们为线程设置一个线程名
    		Thread thread1 = new Thread(runnable);
    		thread1.setName("窗口1");
    
    		Thread thread2 = new Thread(runnable);
    		thread2.setName("窗口2");
    
    		Thread thread3 = new Thread(runnable);
    		thread3.setName("窗口3");
    
    		thread1.start();
    		thread2.start();
    		thread3.start();
    
    	}
    
    }
    

    启动项目之后:
    在这里插入图片描述
    总结1:
      这次电影院将售票系统升级之后,对售票系统进行了改进,当窗口1在使用系统进行卖票的时候(窗口1抢到CPU的执行权),系统就锁定了,窗口2和窗口3无法卖票(等待窗口1释放CPU的执行权),当窗口1将票卖完了之后,系统就会开放(窗口1释放了CPU的执行权),此时,窗口1、窗口2、窗口3又可以使用系统卖票。假如此时窗口3的动作快一点进入了系统(窗口3抢到CPU的执行权,当然窗口1虽然刚用完系统,但是也有可能窗口1又进入系统,相当于窗口1又抢到了CPU的执行权),这样一来,在同一时间内,只会有一个窗口在卖票(同一时间,只有一个线程能够访问某个共享资源,其它线程只能等待抢到CPU执行权的线程释放资源)。也就不会产生电影票的超卖等安全问题(线程安全问题)。

    总结2:
      我们在卖票系统升级的代码中,使用了synchronized(this){……}同步代码块来实现了对卖票系统的锁定(对共享资源的锁定),那么这个this代表的是什么呢?
      答案就是这个this代表了当前正在执行这段代码的线程对象,这个锁就是同步锁。在java中,每个对象有且仅有一个对象锁。那么我们锁定卖票系统的原理是什么呢?
      分析:窗口1执行到这里遇到synchronized同步代码块,检查synchronized是否有锁对象,发现有因为是第一个执行的,进入同步代码块中执行卖票的代码。在窗口1执行的过程中,窗口2来到这里,发现synchronized同步代码块,检查是否有锁对象,发现没有。则窗口2线程进入阻塞状态,等待窗口1线程执行完毕释放锁对象。

    本篇文章是使用synchronized同步锁实现了安全卖票,下一篇文章使用Lock锁实现相同的效果

  • 相关阅读:
    SpringBoot实现原理
    常见Http状态码大全
    forward(转发)和redirect(重定向)有什么区别
    1094. Car Pooling (M)
    0980. Unique Paths III (H)
    1291. Sequential Digits (M)
    0121. Best Time to Buy and Sell Stock (E)
    1041. Robot Bounded In Circle (M)
    0421. Maximum XOR of Two Numbers in an Array (M)
    0216. Combination Sum III (M)
  • 原文地址:https://www.cnblogs.com/wgty/p/12810479.html
Copyright © 2011-2022 走看看