卖票部分问题答疑

Ticket

synchronized基本用法 (重要!)

Java中的任意对象实例都可以作为唯一的锁标志,简单理解就是每一个对象都维护着一个moniter,这个东西跟操作系统底层的mutex互斥量有关,所以任何对于对象锁的争抢可以简单理解为对moniter的争抢,映射到系统底层就是对mutex的争抢(相关深入知识可百度)

1 synchronized 修饰普通方法,则当前线程获得的是调用该方法的对象锁

2 synchronized 修饰静态方法,则当前线程获得的是当前类的Class对象锁

3 synchronized (Object obj) {…} 作用于代码块时比较灵活,获得的是当前 obj 这个对象锁

设计思路

**非最佳思路

1多线程 操作 多个资源

2 多线程 操作 一个资源 但使用了多把锁**

前者显然不符合题目的要求,这个设计本质就是意义不大 不予讨论,重点看很多同学实现出的后一个思路:

下面是部分同学的写法:

class Mythread implements Runnable {
    static int i=100;
    @Override
     public  void run() {
        while(i>0) {
            synchronized (this) {
                if (i > 0) {
                    System.out.println(Thread.currentThread().getName()+"-------"+ i);
                    i--;
                }
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Sold extends Thread {

    public static void main(String args[]) throws InterruptedException {
        Mythread d1=new Mythread();
        Mythread d2=new Mythread();
        Mythread d3=new Mythread();
        Mythread d4=new Mythread();
        Mythread d5=new Mythread();
        Thread t1=new Thread(d1);
        t1.start();
        Thread t2=new Thread(d2);
        t2.start();
        Thread t3=new Thread(d3);
        t3.start();
        Thread t4=new Thread(d4);
        t4.start();
        Thread t5=new Thread(d5);
        t5.start();
    }
}

问题:

1.定义了5个 :d1 d2 d3 d4 d5 , 分别用5个线程去操作它们,本质是不符合多线程访问共享变量的题目要求

2.根据 synchronized 的作用,此代码按理说每个线程不存在锁争抢情况,因为它们都有各自的锁(di),那么理论上应该是出现如下打印情况: 每个线程分别打印100 - 1,相当于总共卖了500个票

3.而实际输出效果如下:
在这里插入图片描述

为什么会这样?

因为虽然这里有5把锁,线程不会争抢锁,但是资源定义是 static int i=100; 静态变量属于类变量,所有对象实例同享同一份,如果把静态变量改成普通变量,就会出现最初的效果:(共打印500此 5个 100 - 1)
在这里插入图片描述
上述这些问题是部分同学在设计出现问题的情况下,跑出来的运行效果以及原因解释。

最佳思路

多线程 操作一个资源,这个资源只用一把锁管理

public class Ticket extends Thread{

    static int num=100;  //剩余票数
    @Override
    public void run() {
        while(num>0) {
            //临界区
            synchronized (this) {
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName()+"_____________"+num);
                	num--;
                }
            }
            //模拟网络延时
            try {
                Thread.sleep(10);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Ticket tic = new Ticket();  //一把锁
        for(int i=1;i<=5;i++) {
            new Thread(tic,""+i).start();
        }
    }
}

可以看到这样 5个线程在访问 唯一共享资源num的时候,使用同一把锁 tic

问题:

1 输出票的序号100 - 1 乱序了,原因:程序逻辑不正确,参考本代码中的处理逻辑

2 票都被一个线程给抢完了,原因:你的CPU太快了 ,没有模拟网络延时,用Thread.sleep

3 输出的序号中最后有 0 -1 -2 …原因:程序逻辑不对,进入临界区后的每个线程先要判断票数合法才执行操作,参考本代码

问题3的解释:假如代码这么写:

public void run() {
        while(num>0) {
            //1
            synchronized (this) {
                //2
                System.out.println(Thread.currentThread().getName()+"_____________"+num);
                num--;
            }
          
            //模拟网络延时
            try {
                Thread.sleep(10);
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
}

当num = 1时,是不是可以有多个线程判断num > 0 成立而进入到1的位置,那么它们后续会依次进入临界区执行 num --,所以就出现了这个错误。

后附上一些可以参考的代码写法

/**
 * 资源类
 */
class TicketOutlet {
    
    public int nums = 100;

    public void sellTicket() {
        synchronized (this) {
            if (nums > 0) {
                //站点卖票
                System.out.println(Thread.currentThread().getName() + "买到了第" + nums + 
                                   "号票,剩余票数为" + --nums);
            }
        }
    }
}

public class ScrambleTicket {

    public static void main(String[] args) {
        //创建一个资源  一个售票点
        TicketOutlet ticketOutlet = new TicketOutlet();

        //5个黄牛抢票
        for (int i = 1; i <= 5; i++) {
            new Thread(() -> {
                //每个黄牛只要发现售票点有票 就一直抢票
                while (ticketOutlet.nums > 0) {
                    //抢票
                    ticketOutlet.sellTicket();
                    //休息
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "黄牛" + i).start();
        }
     }
 }
     
public class Ticket implements Runnable {
    private int number;

    public Ticket() {
        super();
        // TODO Auto-generated constructor stub
        this.number = 100;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (this) {
                notifyAll();
                if (number > 0) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "---------" + number);
                    number--;

                    try {
                        wait();    // 仅为了效果更明显,实际开发售票系统千万别wait()
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值