三、线程的同步实例二:抢票

实例:多线程实现网络购票,用户提交购票信息后,
第一步:网站修改网站车票数据
第二部;显示出票反馈信息给用户
线程类:

public class Site implements Runnable {
//记录剩余票数
    private int count = 10;
    //记录当前抢到第几张票
    private int num = 0;
    
    public void run() {
        //循环,当剩余票数为0时,结束
        while (true) {
            if (count <= 0) {
                break;
            } else {
              // 1、修改数据(剩余票数,抢到第几张票)        
                count--;
              // 2、显示信息,反馈用户抢到第几张票
                num++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            
                System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
            }
        }   
    }   
}

测试类:

public class Test {
    public static void main(String[] args) {
        Site site = new Site();
        Thread person1 = new Thread(site);
        Thread person2 = new Thread(site);
        Thread person3 = new Thread(site);
        person1.start();
        person2.start();
        person3.start();    
    }
}

运行结果:

Thread-2抢到第3张票,剩余7张票
Thread-0抢到第3张票,剩余7张票
Thread-1抢到第3张票,剩余7张票
Thread-2抢到第6张票,剩余4张票
Thread-1抢到第6张票,剩余4张票
Thread-0抢到第6张票,剩余4张票
Thread-2抢到第9张票,剩余1张票
Thread-0抢到第9张票,剩余1张票
Thread-1抢到第9张票,剩余1张票
Thread-2抢到第10张票,剩余0张票

从运行结果看,简直不能看,输出的数据都是些什么东西啊。存在的问题如下:
1、不是从第一张票开始
2、存在多人抢到一张票的情况
3、有些票号没有被抢到
当多个线程共享同一资源时,一个线程未完成全部操作的时候,其他线程修改数据,造成数据的不安全问题。
原因分析:
第一个线程在休眠的1000ms中,其他的两个线程也完成了抢票动作然后进入休眠。等第一个线程一觉醒来一脸懵逼地发现只剩七张票了。剩下两个线程也是这样。
解决思路:将这两步操作绑定在一起,只有当一个线程全部完成这些操作时,才能允许其他线程进行操作。

修改

1、当用synchronized关键字来修改run方法时:

public synchronized void run() {
        //循环,当剩余票数为0时,结束
        while (true) {
            if (count == 0) {
                break;
            } else {
                // 1、修改数据(剩余票数,抢到第几张票)
                count--;
                // 2、显示信息,反馈用户抢到第几张票
                num++;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
            }
        }
 }      

输出结果为:

Thread-0抢到第1张票,剩余9张票
Thread-0抢到第2张票,剩余8张票
Thread-0抢到第3张票,剩余7张票
Thread-0抢到第4张票,剩余6张票
Thread-0抢到第5张票,剩余5张票
Thread-0抢到第6张票,剩余4张票
Thread-0抢到第7张票,剩余3张票
Thread-0抢到第8张票,剩余2张票
Thread-0抢到第9张票,剩余1张票
Thread-0抢到第10张票,剩余0张票

此时第一个线程抢到了全部的票。因为第一个线程一直在run方法中的while中循环,直到票被抢完才会终止。此时运行第二和第三个线程的时候没有票可抢。

2、但是将代码修改为以下时:

public  void run() {
        //循环,当剩余票数为0时,结束
        while (true) {
            if (count <= 0) {
                break;
            } else {
                this.a();
            }
        }

      } 
    
    
    synchronized public void a() {
        // 1、修改数据(剩余票数,抢到第几张票)
        count--;
        // 2、显示信息,反馈用户抢到第几张票
        num++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
    }

输出结果为:

Thread-0抢到第1张票,剩余9张票
Thread-2抢到第2张票,剩余8张票
Thread-1抢到第3张票,剩余7张票
Thread-1抢到第4张票,剩余6张票
Thread-1抢到第5张票,剩余5张票
Thread-1抢到第6张票,剩余4张票
Thread-1抢到第7张票,剩余3张票
Thread-1抢到第8张票,剩余2张票
Thread-1抢到第9张票,剩余1张票
Thread-1抢到第10张票,剩余0张票
Thread-2抢到第11张票,剩余-1张票
Thread-0抢到第12张票,剩余-2张票

从结果来看,多抢了两张票。
当把if的控制条件改为以下时:

public  void run() {
        //循环,当剩余票数为0时,结束
        while (true) {
            if (count <= 2) {
                break;
            } else {
                this.a();
            }
        }
}   

输出结果为:

Thread-0抢到第1张票,剩余9张票
Thread-2抢到第2张票,剩余8张票
Thread-2抢到第3张票,剩余7张票
Thread-1抢到第4张票,剩余6张票
Thread-1抢到第5张票,剩余5张票
Thread-2抢到第6张票,剩余4张票
Thread-2抢到第7张票,剩余3张票
Thread-2抢到第8张票,剩余2张票
Thread-0抢到第9张票,剩余1张票
Thread-1抢到第10张票,剩余0张票

此时的结果没问题,但是为什么啊?在我看来,控制条件无论如何也不应该是 <= 2。
原因可能如下:三个线程都会运行run方法,在运行过程中都走过了if的判断条件,只是在else中的语句前停下,因为else中的语句设置了锁,在他们等待运行的时间中,票数可能就改变了,变得小于0了,但是他们还是会运行完自己的语句。直到下一次运行进不去else的条件时为止。
同时,如果将条件改为count == 0的话,结果可能会更糟,因为在他们等待的时间中,票数可能已经变化过去0了,一旦这样的话就会陷入到一个死循环中。
3、同时这样改也没问题:定义了一个flag来控制while循环条件

public class Site implements Runnable {
//记录剩余票数
    private int count = 10;
    //记录当前抢到第几张票
    private int num = 0;
    boolean flag = false;
    
    public  void run() {
        //循环,当剩余票数为0时,结束
        while (!false) {
            this.a();
        }

      } 
    
    
    synchronized public void a() {
        if (count <= 0) {
            flag = true;
            return;
        } else {
            
        }
        // 1、修改数据(剩余票数,抢到第几张票)
        count--;
        // 2、显示信息,反馈用户抢到第几张票
        num++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
    }
}

此时结果为:

Thread-0抢到第1张票,剩余9张票
Thread-2抢到第2张票,剩余8张票
Thread-2抢到第3张票,剩余7张票
Thread-2抢到第4张票,剩余6张票
Thread-1抢到第5张票,剩余5张票
Thread-1抢到第6张票,剩余4张票
Thread-1抢到第7张票,剩余3张票
Thread-2抢到第8张票,剩余2张票
Thread-2抢到第9张票,剩余1张票
Thread-2抢到第10张票,剩余0张票

4、使用修改代码块的方式来修改如下:

public class Site implements Runnable {
//记录剩余票数
    private int count = 10;
    //记录当前抢到第几张票
    private int num = 0;
    boolean flag = false;
    
    public  void run() {
        //循环,当剩余票数为0时,结束
        while (!flag) {
            synchronized (this) {   
                this.a();
            }
        }
    }   
    
    
    synchronized public void a() {
        if (count <= 0) {
            flag = true;
            return;
        } else {
            // 1、修改数据(剩余票数,抢到第几张票)
        count--;
        // 2、显示信息,反馈用户抢到第几张票
        num++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票");
        }
    }
}

运行结果如下:

Thread-0抢到第1张票,剩余9张票
Thread-0抢到第2张票,剩余8张票
Thread-0抢到第3张票,剩余7张票
Thread-0抢到第4张票,剩余6张票
Thread-0抢到第5张票,剩余5张票
Thread-0抢到第6张票,剩余4张票
Thread-2抢到第7张票,剩余3张票
Thread-1抢到第8张票,剩余2张票
Thread-1抢到第9张票,剩余1张票
Thread-1抢到第10张票,剩余0张票

总结

多个并发线程访问同一资源的同步代码块时
1、同一时刻只能有一个线程进入synchronized同步代码块。
2、当一个线程访问一个synchronized(this),其他synchronized(this)同步代码块同样被锁定
3、当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码。

什么锁,其实就是个厕所,你不出来我就进不去。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 30,030评论 8 265
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 176,663评论 25 709
  • 今天是正常上班的一天,从懒散步入忙碌的工作状态!在工作的同时也学到更多的知识!2018年新年新气象,努力把工作做好...
    满满_3a51阅读 1,675评论 0 1
  • 论语里面说过“吾十有五而志于学,三十而立,四十而不惑,五十而知天命,六十而耳顺,七十而从心所欲,不逾矩。”能够做到...
    陳志宇阅读 5,477评论 1 15
  • 我开心过,因为你;我痛哭过,因为你;我努力向上,只是因为对女人而言,从来没有感性和理性的较量,感性永...
    孟小苒阅读 3,523评论 5 3

友情链接更多精彩内容