三、線程的同步實例二:搶票

實例:多線程實現(xiàn)網(wǎng)絡購票,用戶提交購票信息后,
第一步:網(wǎng)站修改網(wǎng)站車票數(shù)據(jù)
第二部;顯示出票反饋信息給用戶
線程類:

public class Site implements Runnable {
//記錄剩余票數(shù)
    private int count = 10;
    //記錄當前搶到第幾張票
    private int num = 0;
    
    public void run() {
        //循環(huán),當剩余票數(shù)為0時,結束
        while (true) {
            if (count <= 0) {
                break;
            } else {
              // 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)        
                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張票

從運行結果看,簡直不能看,輸出的數(shù)據(jù)都是些什么東西啊。存在的問題如下:
1、不是從第一張票開始
2、存在多人搶到一張票的情況
3、有些票號沒有被搶到
當多個線程共享同一資源時,一個線程未完成全部操作的時候,其他線程修改數(shù)據(jù),造成數(shù)據(jù)的不安全問題。
原因分析:
第一個線程在休眠的1000ms中,其他的兩個線程也完成了搶票動作然后進入休眠。等第一個線程一覺醒來一臉懵逼地發(fā)現(xiàn)只剩七張票了。剩下兩個線程也是這樣。
解決思路:將這兩步操作綁定在一起,只有當一個線程全部完成這些操作時,才能允許其他線程進行操作。

修改

1、當用synchronized關鍵字來修改run方法時:

public synchronized void run() {
        //循環(huán),當剩余票數(shù)為0時,結束
        while (true) {
            if (count == 0) {
                break;
            } else {
                // 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
                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中循環(huán),直到票被搶完才會終止。此時運行第二和第三個線程的時候沒有票可搶。

2、但是將代碼修改為以下時:

public  void run() {
        //循環(huán),當剩余票數(shù)為0時,結束
        while (true) {
            if (count <= 0) {
                break;
            } else {
                this.a();
            }
        }

      } 
    
    
    synchronized public void a() {
        // 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
        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() {
        //循環(huán),當剩余票數(shù)為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中的語句設置了鎖,在他們等待運行的時間中,票數(shù)可能就改變了,變得小于0了,但是他們還是會運行完自己的語句。直到下一次運行進不去else的條件時為止。
同時,如果將條件改為count == 0的話,結果可能會更糟,因為在他們等待的時間中,票數(shù)可能已經(jīng)變化過去0了,一旦這樣的話就會陷入到一個死循環(huán)中。
3、同時這樣改也沒問題:定義了一個flag來控制while循環(huán)條件

public class Site implements Runnable {
//記錄剩余票數(shù)
    private int count = 10;
    //記錄當前搶到第幾張票
    private int num = 0;
    boolean flag = false;
    
    public  void run() {
        //循環(huán),當剩余票數(shù)為0時,結束
        while (!false) {
            this.a();
        }

      } 
    
    
    synchronized public void a() {
        if (count <= 0) {
            flag = true;
            return;
        } else {
            
        }
        // 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
        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 {
//記錄剩余票數(shù)
    private int count = 10;
    //記錄當前搶到第幾張票
    private int num = 0;
    boolean flag = false;
    
    public  void run() {
        //循環(huán),當剩余票數(shù)為0時,結束
        while (!flag) {
            synchronized (this) {   
                this.a();
            }
        }
    }   
    
    
    synchronized public void a() {
        if (count <= 0) {
            flag = true;
            return;
        } else {
            // 1、修改數(shù)據(jù)(剩余票數(shù),搶到第幾張票)
        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張票

總結

多個并發(fā)線程訪問同一資源的同步代碼塊時
1、同一時刻只能有一個線程進入synchronized同步代碼塊。
2、當一個線程訪問一個synchronized(this),其他synchronized(this)同步代碼塊同樣被鎖定
3、當一個線程訪問一個synchronized(this)同步代碼塊時,其他線程可以訪問該資源的非synchronized(this)同步代碼。

什么鎖,其實就是個廁所,你不出來我就進不去。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結構(3).初始化時...
    歐辰_OSR閱讀 30,246評論 8 265
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,078評論 25 709
  • 今天是正常上班的一天,從懶散步入忙碌的工作狀態(tài)!在工作的同時也學到更多的知識!2018年新年新氣象,努力把工作做好...
    滿滿_3a51閱讀 263評論 0 1
  • 論語里面說過“吾十有五而志于學,三十而立,四十而不惑,五十而知天命,六十而耳順,七十而從心所欲,不逾矩?!蹦軌蜃龅?..
    陳志宇閱讀 1,647評論 1 15
  • 我開心過,因為你;我痛哭過,因為你;我努力向上,只是因為對女人而言,從來沒有感性和理性的較量,感性永...
    孟小苒閱讀 586評論 5 3

友情鏈接更多精彩內(nèi)容