• 04-线程安全问题 的出现


    案例1:

      电影院 4个窗口 售卖50张电影票

    package com.example02;
    public class Ticket01 extends Thread {   
    /* 模拟火车站窗口售票场景: 4个窗口发售 某次列车的50张车票
    *可以将50张车票 看作是共享资源
    *4个窗口,则需要创建4个线程
    */
        private int tickets=50;
        private String name;
        
        public Ticket01(String name) {
            this.name=name;
        }
        public void run() {
            
            while(true) {                      //通过死循环语句打印语句
                if(tickets>0) {       
                    //while循环依次对ticket属性进行自减操作,以模拟售票的过程,直到ticket属性值为0时,跳出循环
                    System.out.println(name+"正在发售第"+  tickets--  +"张票");  
                }else {
                    break;
                }
            }
        }
        public static void main(String[] args) {   // 定义4个线程对象,代表4个售票窗口
            
            Ticket01 t1 = new Ticket01("窗口1");
            Ticket01 t2 = new Ticket01("窗口2");
            Ticket01 t3 = new Ticket01("窗口3");
            Ticket01 t4 = new Ticket01("窗口4");
            t1.start();                             //启动4个线程
            t2.start();
            t3.start();
            t4.start();
        }
    
    }

    结果

    窗口2正在发售第50张票
    窗口1正在发售第50张票
    窗口1正在发售第49张票
    窗口1正在发售第48张票
    窗口1正在发售第47张票
    窗口1正在发售第46张票
    窗口1正在发售第45张票
    窗口1正在发售第44张票
    窗口1正在发售第43张票
    窗口1正在发售第42张票
    窗口1正在发售第41张票
    窗口1正在发售第40张票
    窗口1正在发售第39张票
    窗口1正在发售第38张票
    窗口1正在发售第37张票
    窗口1正在发售第36张票
    窗口1正在发售第35张票
    窗口1正在发售第34张票
    窗口1正在发售第33张票
    窗口1正在发售第32张票
    窗口1正在发售第31张票
    窗口1正在发售第30张票
    窗口1正在发售第29张票
    窗口1正在发售第28张票
    窗口1正在发售第27张票
    窗口1正在发售第26张票
    窗口1正在发售第25张票
    窗口1正在发售第24张票
    窗口1正在发售第23张票
    窗口1正在发售第22张票
    窗口1正在发售第21张票
    窗口1正在发售第20张票
    窗口1正在发售第19张票
    窗口1正在发售第18张票
    窗口1正在发售第17张票
    窗口1正在发售第16张票
    窗口1正在发售第15张票
    窗口1正在发售第14张票
    窗口1正在发售第13张票
    窗口1正在发售第12张票
    窗口1正在发售第11张票
    窗口1正在发售第10张票
    窗口1正在发售第9张票
    窗口1正在发售第8张票
    窗口1正在发售第7张票
    窗口1正在发售第6张票
    窗口1正在发售第5张票
    窗口1正在发售第4张票
    窗口1正在发售第3张票
    窗口1正在发售第2张票
    窗口1正在发售第1张票
    窗口2正在发售第49张票
    窗口2正在发售第48张票
    窗口2正在发售第47张票
    窗口2正在发售第46张票
    窗口3正在发售第50张票
    窗口2正在发售第45张票
    窗口2正在发售第44张票
    窗口2正在发售第43张票
    窗口2正在发售第42张票
    窗口2正在发售第41张票
    窗口2正在发售第40张票
    窗口2正在发售第39张票
    窗口2正在发售第38张票
    窗口2正在发售第37张票
    窗口2正在发售第36张票
    窗口2正在发售第35张票
    窗口2正在发售第34张票
    窗口2正在发售第33张票
    窗口2正在发售第32张票
    窗口2正在发售第31张票
    窗口2正在发售第30张票
    窗口2正在发售第29张票
    窗口2正在发售第28张票
    窗口2正在发售第27张票
    窗口2正在发售第26张票
    窗口2正在发售第25张票
    窗口2正在发售第24张票
    窗口2正在发售第23张票
    窗口2正在发售第22张票
    窗口2正在发售第21张票
    窗口2正在发售第20张票
    窗口2正在发售第19张票
    窗口2正在发售第18张票
    窗口2正在发售第17张票
    窗口2正在发售第16张票
    窗口2正在发售第15张票
    窗口2正在发售第14张票
    窗口2正在发售第13张票
    窗口2正在发售第12张票
    窗口2正在发售第11张票
    窗口2正在发售第10张票
    窗口3正在发售第49张票
    窗口2正在发售第9张票
    窗口2正在发售第8张票
    窗口2正在发售第7张票
    窗口2正在发售第6张票
    窗口2正在发售第5张票
    窗口2正在发售第4张票
    窗口2正在发售第3张票
    窗口2正在发售第2张票
    窗口2正在发售第1张票
    窗口3正在发售第48张票
    窗口3正在发售第47张票
    窗口3正在发售第46张票
    窗口3正在发售第45张票
    窗口3正在发售第44张票
    窗口3正在发售第43张票
    窗口3正在发售第42张票
    窗口3正在发售第41张票
    窗口3正在发售第40张票
    窗口3正在发售第39张票
    窗口3正在发售第38张票
    窗口3正在发售第37张票
    窗口3正在发售第36张票
    窗口3正在发售第35张票
    窗口3正在发售第34张票
    窗口3正在发售第33张票
    窗口3正在发售第32张票
    窗口3正在发售第31张票
    窗口3正在发售第30张票
    窗口4正在发售第50张票
    窗口3正在发售第29张票
    窗口4正在发售第49张票
    窗口3正在发售第28张票
    窗口4正在发售第48张票
    窗口3正在发售第27张票
    窗口4正在发售第47张票
    窗口3正在发售第26张票
    窗口4正在发售第46张票
    窗口3正在发售第25张票
    窗口4正在发售第45张票
    窗口3正在发售第24张票
    窗口4正在发售第44张票
    窗口3正在发售第23张票
    窗口4正在发售第43张票
    窗口3正在发售第22张票
    窗口4正在发售第42张票
    窗口3正在发售第21张票
    窗口4正在发售第41张票
    窗口3正在发售第20张票
    窗口4正在发售第40张票
    窗口3正在发售第19张票
    窗口4正在发售第39张票
    窗口3正在发售第18张票
    窗口4正在发售第38张票
    窗口3正在发售第17张票
    窗口4正在发售第37张票
    窗口3正在发售第16张票
    窗口4正在发售第36张票
    窗口3正在发售第15张票
    窗口4正在发售第35张票
    窗口3正在发售第14张票
    窗口4正在发售第34张票
    窗口3正在发售第13张票
    窗口4正在发售第33张票
    窗口3正在发售第12张票
    窗口4正在发售第32张票
    窗口3正在发售第11张票
    窗口4正在发售第31张票
    窗口3正在发售第10张票
    窗口4正在发售第30张票
    窗口3正在发售第9张票
    窗口4正在发售第29张票
    窗口4正在发售第28张票
    窗口4正在发售第27张票
    窗口4正在发售第26张票
    窗口4正在发售第25张票
    窗口4正在发售第24张票
    窗口4正在发售第23张票
    窗口4正在发售第22张票
    窗口4正在发售第21张票
    窗口4正在发售第20张票
    窗口4正在发售第19张票
    窗口4正在发售第18张票
    窗口4正在发售第17张票
    窗口4正在发售第16张票
    窗口4正在发售第15张票
    窗口4正在发售第14张票
    窗口3正在发售第8张票
    窗口4正在发售第13张票
    窗口4正在发售第12张票
    窗口4正在发售第11张票
    窗口4正在发售第10张票
    窗口4正在发售第9张票
    窗口3正在发售第7张票
    窗口4正在发售第8张票
    窗口3正在发售第6张票
    窗口4正在发售第7张票
    窗口3正在发售第5张票
    窗口4正在发售第6张票
    窗口3正在发售第4张票
    窗口4正在发售第5张票
    窗口3正在发售第3张票
    窗口4正在发售第4张票
    窗口3正在发售第2张票
    窗口3正在发售第1张票
    窗口4正在发售第3张票
    窗口4正在发售第2张票
    窗口4正在发售第1张票

     出现问题:

     总共50张票,但是结果显示:每个窗口售卖了50张票,一共售出200张票!!!!

    改进代码:

    package com.example02;
    public class Ticket02 implements Runnable {   
        private int tickets=50;
         
        public void run() {
            
            while(true) {                                           //通过死循环语句打印语句
                if(tickets>0) {       
                    
                    String name=Thread.currentThread().getName();   //获取当前线程的名称
                    
                    //while循环依次对ticket属性进行自减操作,以模拟售票的过程,直到ticket属性值为0时,跳出循环
                    System.out.println(name+"正在发售第"+  tickets--  +"张票");  
                    
                }else {
                    break;
                }
            }
        }
        public static void main(String[] args) {  
            
            //创建线程任务
            Ticket02 ticket02 = new Ticket02();
            // 定义4个线程对象,代表4个售票窗口
            Thread t1 = new Thread(ticket02,"窗口1");
            Thread t2 = new Thread(ticket02,"窗口2");
            Thread t3 = new Thread(ticket02,"窗口3");
            Thread t4 = new Thread(ticket02,"窗口4");
            //开启四个线程
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            
             
        }
    
    }

     结果:

    窗口1正在发售第50张票
    窗口3正在发售第49张票
    窗口3正在发售第48张票
    窗口3正在发售第47张票
    窗口3正在发售第46张票
    窗口3正在发售第45张票
    窗口3正在发售第43张票
    窗口3正在发售第42张票
    窗口3正在发售第41张票
    窗口3正在发售第40张票
    窗口3正在发售第39张票
    窗口3正在发售第38张票
    窗口3正在发售第37张票
    窗口3正在发售第36张票
    窗口3正在发售第35张票
    窗口3正在发售第34张票
    窗口3正在发售第33张票
    窗口1正在发售第44张票
    窗口1正在发售第29张票
    窗口1正在发售第28张票
    窗口1正在发售第27张票
    窗口1正在发售第26张票
    窗口1正在发售第25张票
    窗口1正在发售第24张票
    窗口1正在发售第23张票
    窗口1正在发售第22张票
    窗口1正在发售第21张票
    窗口1正在发售第20张票
    窗口1正在发售第19张票
    窗口4正在发售第30张票
    窗口4正在发售第17张票
    窗口4正在发售第16张票
    窗口4正在发售第15张票
    窗口4正在发售第14张票
    窗口4正在发售第13张票
    窗口4正在发售第12张票
    窗口4正在发售第11张票
    窗口4正在发售第10张票
    窗口4正在发售第9张票
    窗口4正在发售第8张票
    窗口4正在发售第7张票
    窗口4正在发售第6张票
    窗口4正在发售第5张票
    窗口4正在发售第4张票
    窗口4正在发售第3张票
    窗口4正在发售第2张票
    窗口4正在发售第1张票
    窗口3正在发售第31张票
    窗口2正在发售第32张票
    窗口1正在发售第18张票

    解决了问题:4个窗口共买了50张电影票


    案例2:

    1.【模拟银行ATM机取钱时(一个线程)】
     
    package com.monkey1030;
    
    public class Account implements Runnable {
        
        private int money = 1000; //账户余额
    
        @Override
        public void run() {
            
             
                
                // 模拟ATM机器 去后台服务器读取账户余额的过程
                
                int temp = money;   // 1000
                
                // 判断账户余额是否 >=200
                if(temp >= 200) {
                    // 模拟取款过程
                    System.out.println("正在出钞,请稍后....");
                    temp -= 200;   // 账户余额扣200:  1000 - 200 = 800
                    
                    try {
                        
                        Thread.sleep(3000);  // 休眠3秒
                        
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("请取走您的钞票!");
                    
                    
                }else {
                    
                    System.out.println("账户余额不足,请重新核对金额!");
                    
                }
                money = temp;
             
    
        }
    
        public int getMoney() {
            return money;
        }
    
        public void setMoney(int money) {
            this.money = money;
        }
    
    }

    测试:

    package com.monkey1030;
    
    public class Test {
    
        public static void main(String[] args) {
            
            Account acc = new Account();
            
            Thread t1 = new Thread(acc);   // 创建线程t1
             
            
            t1.start();   // 开启线程 t1
            
            try {
                
                // 让主线程等待子线程终止,然后再打印账户余额
                
                t1.join();
                 
                
            } catch (InterruptedException e) {
                
                e.printStackTrace();
            }
            
            System.out.println("最后的账户余额是 :"+acc.getMoney());
            
            
            
            
    
        }
    
    }

    结果:

    正在出钞,请稍后....
    请取走您的钞票!
    最后的账户余额是 :800

    案例3:

    【模拟银行ATM机取钱时(两个线程同时运行)】
    观察控制台的 结果:出现的问题是 金额不对

    改动测试代码即可:

    package com.monkey1030;
    
    public class Test {
    
        public static void main(String[] args) {
            
            Account acc=new Account();
            Thread t1=new Thread(acc);
            Thread t2=new Thread(acc);
            
             
            t1.start();    
            t2.start();
             
            //让主线程 等待子线程终止,然后打印账户余额
            try {
                t1.join();
                t2.join();
                 
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            
            System.out.println("账户余额是: "+acc.getMoney());
            
            
            
    
        }
    
    }

    结果:

    正在出钞,请稍后....
    正在出钞,请稍后....
    请取走您的钞票!
    请取走您的钞票!
    账户余额是: 800
    

    这样的结果显然是错误的,取了2次钱,每次取了200.应该还剩余额 600元!!!

    分析:

    关于对控制台结果的质疑,进行代码分析:
    默认时候, temp=money=1000;
    按道理:t1线程取钱之后,需执行temp-=200; temp就变成了800;
    然后t2线程再在 temp=800;的基础上取钱。 t2 取钱之后,temp -=200; 余额 =600. 因为 线程 t1 和 线程 t2 ,是同时执行的。
    问题出在 temp 这个变量上。 即,当t1线程 取钱200之后,还没来得及 进行 temp-=200;这一步赋值操作。
    也就是 temp的值还没来得及 更新,t2线程就访问到了temp原来的变量1000. 所以造成 1000-200=800的 结果。

     
    由以上代码演示存在的安全问题,提出来的 线程同步
  • 相关阅读:
    Xenserver中SR、VBD和VDI之间的关系
    rabbitmq镜像模式设置策略以及高可用
    MySQL主从复制配置详解
    xen 虚拟机挂了,宿主机假死的问题追终,全思路
    XenServer中备份正在运行的虚拟机
    理解 Docker 容器退出码
    prometheus 监控之 进程监控(processexporter)
    XenServer 常见故障处理
    httpCurl封装
    工作中不要为了用系统而用系统
  • 原文地址:https://www.cnblogs.com/penguin1024/p/11781521.html
Copyright © 2020-2023  润新知