多线程数据安全问题--卖票

1、Thread对象与Runnable接口

  1. Thread实现步骤少
  2. Runnable将线程 和 在线程上执行的任务解耦
  3. Thread的实现方式,存在单重继承的局限性
  4. Runnable,便于多线程数据的共享(电影院卖票)

2、场景

假设A电影院正在上映某电影,该电影有100张电影票可供出售,
现在假设有3个窗口售票。请设计程序模拟窗口售票的场景。

public class SalesDemo {
    public static void main(String[] args) {
        //第一种方式
//        SalesWindow salesWindow1 = new SalesWindow();
//        SalesWindow salesWindow2 = new SalesWindow();
//        SalesWindow salesWindow3 = new SalesWindow();
//
//        salesWindow1.start();
//        salesWindow2.start();
//        salesWindow3.start();

        //第二种方式,基本可以模拟我们的售票场景(数据共享)
        SalesTask salesTask = new SalesTask();

        new Thread(salesTask).start();
        new Thread(salesTask).start();
        new Thread(salesTask).start();
    }
}

//第一种实现方式
class SalesWindow extends Thread{
    int tickets = 100;
    //static int tickets = 100;//只有加static关键字才能实现三个线程共享100张票

    @Override
    public void run() {
        //执行售票的动作
        System.out.println(getName()+"售卖了第"+tickets+"张票");
    }
}

//第二种实现方式
class SalesTask implements Runnable{
    int tickets = 100;

    @Override
    public void run() {
        //执行售票的动作
        while (tickets > 0){
            System.out.println(Thread.currentThread().getName()+"售卖出了第"+tickets--+"张票");
        }
    }
}

出现的问题:
加入售票延迟后,再次运行我们的仿真程序,此时我们就发现了问题:

  1. 相同的票,卖了多次(多卖)
    窗口1卖出第99张票
    窗口2卖出第99张票
  2. 卖出了不存在的票(即超卖)
    窗口1卖出第1张票
    窗口3卖出第0张票

即在多线程运行环境下,多个线程,"同时"访问共享数据,访问到了错误的结果,这就是所谓多线程数据安全问题

共享变量加锁–同步代码块

public class Demo3 {
    public static void main(String[] args) {

        //第二种方式,基本可以模拟我们的售票场景(数据共享)
        SalesT1 salesTask = new SalesT1();

        new Thread(salesTask,"窗口1").start();
        new Thread(salesTask,"窗口2").start();
        new Thread(salesTask,"窗口3").start();
    }
}

//第二种实现方式
class SalesT1 implements Runnable {
    int tickets = 100;
    //作为锁对象的java对象
    private Object lockObj = new Object();

    @Override
    public void run() {
            while (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lockObj){
                    if (tickets > 0){//double check,防止超卖
                        System.out.println(Thread.currentThread().getName() + "售卖出了第" + tickets-- + "张票");
                    }
                }
            }
    }
}
--------------------------------
        必须为同一个锁对象
//      if (i % 2 == 1) {
//         // i是奇数
//        i++;
//        synchronized (lockObj1) {
//          if (tickets > 0) {// double check
//            System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
//          }
//        }
//      } else {
//        // i是偶数
//        i++;
//        synchronized (lockObj1) {
//          if (tickets > 0) { // double check
//            System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
//          }
//        }
//      }

共享变量加锁–方法

public class Sales {

  public static void main(String[] args) {

    //创建Runnable子类对象
    SalesTask salesTask = new SalesTask();

    //启动3个线程,模拟3个售票窗口, 实现了三个窗口,售卖共同的100张票
    Thread window1 = new Thread(salesTask, "窗口1");
    Thread window2 = new Thread(salesTask, "窗口2");
    Thread window3 = new Thread(salesTask, "窗口3");

    window1.start();
    window2.start();
    window3.start();
  }
}
/*
    定义SalesWindow表示,售票窗口
*/
class SalesTask implements Runnable {
  //表示被售卖的100张票
  int tickets = 100;

  private Object lockObj = new Object();

  @Override
  public void run() {

    while (tickets > 0) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      //调用方法
      this.sale();

      //  该run方法 target.run()
      //  因此this指的当前对象,其实就是在三个线程中运行的,同一个SalesTask
//      synchronized (this) {
//        // 假设现在窗口1加锁成功
//        if (tickets > 0) {
//          System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
//        }
//      }
    }
  }

  // 普通成员方法的锁对象就是其当前对象this, 对象名.方法
  private synchronized void sale() {

    if (tickets > 0) {
      System.out.println(Thread.currentThread().getName() + "卖出第" + tickets-- + "张票");
    }
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值