Java多執行緒/併發05、synchronized應用例項:執行緒間操作共享資料

唐大麥發表於2017-04-28

電商平臺中最重要的一點就是賣東西。同個商品不能無限制的賣下去的,因為商品有庫存量,超過庫存就不能賣了。
這裡,約定一個規則,下單使庫存減n,取消訂單使庫存加m。庫存數量不可以小於0。
假設平臺上同時有很多使用者在操作,在不考慮效率的情況下,我們用同步方法來模擬這個場景。

首先寫一個訂單處理類:

class OrderHandler{
    /*初始某商品庫存量*/
    int StockSomeGoodsNum=200;
    /*使用者下單*/
    public void Produce(int n){
        /*step1:判斷可用庫存操作*/
        if((StockSomeGoodsNum-n)>=0){
            /*為了更好體現執行緒間的競爭,讓程式休眠一下*/
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /*step2:執行減少庫存操作*/
            StockSomeGoodsNum-=n;
            System.out.println("使用者" + Thread.currentThread().getName()
                    + "成功購買商品:" + String.valueOf(n)+"個,庫存剩餘"+StockSomeGoodsNum+"個");
        }else{
            System.out.println("使用者" + Thread.currentThread().getName()
                    + "下單失敗,庫存不足" + String.valueOf(n)+"個,庫存剩餘"+StockSomeGoodsNum+"個");
        }
    }
  /*使用者取消訂單*/
    public void Cancel(int n){
        StockSomeGoodsNum+=n;
        System.out.println("使用者" + Thread.currentThread().getName()
                + "取消購買商品:" + String.valueOf(n)+"個,庫存剩餘"+StockSomeGoodsNum+"個");
    }

}

可以看到類中的兩個方法都會操作StockSomeGoodsNum變數
在使用者下單方法void Produce(int n)中,首先判斷庫存數量夠不夠,如果夠了對庫存數量進行減少操作。這裡涉及兩個步驟:判斷庫存、減少庫存。這兩個步驟組成一個事務,是不允許分開和打亂的。但現在是多個執行緒併發執行,就有可能在這兩個步驟之間,有別的執行緒執行了增減庫存操作。

現在在主函式中開啟多個執行緒來模擬使用者下單和取消訂單的操作:

public static void main(String[] args) {
        final OrderHandler orderHandler=new OrderHandler();
        /*開啟10個執行緒,模擬10個使用者進行下單操作*/
        for(int i=0;i<10;i++){
            new Thread(new Runnable() {
                public void run() {
                    /*每個人購買商品數量為25個*/
                    orderHandler.Produce(25);
                }
            }).start();
        }
        /*開啟5個執行緒,模擬5個使用者在進行取消訂單操作*/
        for(int i=0;i<5;i++){
            new Thread(new Runnable() {
                public void run() {
                    /*每個取消訂單中包含的商品數為3個*/
                    orderHandler.Cancel(3);
                }
            }).start();
        }
    }
}

執行結果:

使用者Thread-10取消購買商品:3個,庫存剩餘203個
使用者Thread-12取消購買商品:3個,庫存剩餘209個
使用者Thread-11取消購買商品:3個,庫存剩餘206個
使用者Thread-13取消購買商品:3個,庫存剩餘212個
使用者Thread-14取消購買商品:3個,庫存剩餘215個
使用者Thread-1成功購買商品:25個,庫存剩餘165個
使用者Thread-2成功購買商品:25個,庫存剩餘140個
使用者Thread-0成功購買商品:25個,庫存剩餘165個
使用者Thread-3成功購買商品:25個,庫存剩餘115個
使用者Thread-4成功購買商品:25個,庫存剩餘65個
使用者Thread-5成功購買商品:25個,庫存剩餘65個
使用者Thread-8成功購買商品:25個,庫存剩餘-10個
使用者Thread-6成功購買商品:25個,庫存剩餘15個
使用者Thread-7成功購買商品:25個,庫存剩餘-10個
使用者Thread-9成功購買商品:25個,庫存剩餘-35

出現了負數,說明在判斷庫存、減少庫存的兩個步驟中有其它執行緒執行,導致結果不正確。現在我們在Produce()和Cancle()方法前加上synchronized關鍵字,讓它們成為同步方法。
再次執行結果:

使用者Thread-0成功購買商品:25個,庫存剩餘175個
使用者Thread-14取消購買商品:3個,庫存剩餘178個
使用者Thread-13取消購買商品:3個,庫存剩餘181個
使用者Thread-12取消購買商品:3個,庫存剩餘184個
使用者Thread-11取消購買商品:3個,庫存剩餘187個
使用者Thread-10取消購買商品:3個,庫存剩餘190個
使用者Thread-9成功購買商品:25個,庫存剩餘165個
使用者Thread-8成功購買商品:25個,庫存剩餘140個
使用者Thread-7成功購買商品:25個,庫存剩餘115個
使用者Thread-6成功購買商品:25個,庫存剩餘90個
使用者Thread-5成功購買商品:25個,庫存剩餘65個
使用者Thread-4成功購買商品:25個,庫存剩餘40個
使用者Thread-3成功購買商品:25個,庫存剩餘15個
使用者Thread-2下單失敗,庫存不足25個,庫存剩餘15個
使用者Thread-1下單失敗,庫存不足25個,庫存剩餘15個

結論:

各個執行緒間能保持如此好的協作,其關鍵原因就是OrderHandler類中的方法都是synchronized同步方法,因此他們有共同的鎖:this,當一個執行緒執行時,其它執行緒都被阻塞,這樣保證了程式的正常執行。
synchronized同步方法雖然很安全,但效率也很低。synchronized同步方法是序列執行的,多個執行緒排隊,並沒有併發執行。因此在實際業務開發中要注意。

相關文章