• 线程安全问题经典案例---卖票


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

    案例场景
    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锁实现相同的效果

  • 相关阅读:
    Project Euler Problem 26-Reciprocal cycles
    Project Euler Problem 24-Lexicographic permutations
    Project Euler Problem 23-Non-abundant sums
    AtCoder Beginner Contest 077 D Small Multiple(最短路)
    浮点数表示及其实现
    ACM water
    Makefile经典教程(掌握这些足够)
    Linux makefile 教程 非常详细,且易懂
    C/C++中const的用法 分类: C/C++ 2015-07-05 00:43 85人阅读 评论(0) 收藏
    自动化测试工具QTP的使用实例 分类: 软件测试 2015-06-17 00:23 185人阅读 评论(0) 收藏
  • 原文地址:https://www.cnblogs.com/wgty/p/12810479.html
Copyright © 2020-2023  润新知