標籤:
開發過程中,有時候為瞭解決多線程競爭問題需要加鎖,通常鎖定的對象是class,object,method,但在特定時候我們需要更細粒度的加鎖,也就是根據不同輸入參數來鎖定不同的資源,這樣只有調用此方法的不同線程傳參一樣才會進行競爭。
比如一個簡單的例子:假設系統為使用者提供借款,每月有個限額。每月的借款記錄都記在transaction_detail表中,此時當一個使用者需要進行借款的時候,我們需要進行一下操作:
method:borrow_money
1、判斷使用者是否達到借款限額,本月已借款量:select sum(amount) from transaction_detail where user_id=XX;
2、其他業務處理
3、插入transaction_detail
但是以上三步並非原子的,也就是假設使用者限額為3w,已借款量1.5w(還可借1.5w),現在有兩個線程a、b,每個線程都需要借款1w。
當線程a執行完第一步判斷,此時線程a掛起。
線程2開始執行,由於此時借款1w<可借的1.5w,於是線程2執行下去成功貸款1w,插如新的貸款記錄。
a線程此時繼續執行2、3步也成功貸款了1w,這樣該使用者當月貸款量已達3.5w大於總限額了(違背了每月3w的限額)。導致這個情況就是以上3步非原子的,在我們執行借款、插入借款記錄的貸款狀態很可能與我們判斷當月已貸款量已經不同了。
這樣我們就需要對上面的三步進行加鎖同步,但是如果我們將上訴方法borrow_money使用synchronized,將會引發很嚴重的效率問題,因為這樣整個系統的所有線程在執行borrow_money都是競爭關係的,這樣就會造成系統效能瓶頸。
於是我們可以使用一種更細粒度的加鎖機制。可以對特定字串加鎖來保證操作的原子性同時減少線程對資源的競爭,這個特定字串需要與使用者一對一的關聯,同時確保不會影響系統其它操作,所以可以使用:特殊字元串(確保字串具有特殊性) + id。
而且我們的字串需要到jvm的常量池中擷取這樣確保對於相同的使用者擷取的字串是一個對象。
String syncKey = ("borrow_money_" + userId).intern();synchronized (syncKey) { //1、判斷使用者本月已借款量:select sum(amount) from transaction_detail where user_id=XX; //2、其他業務處理 //3、插入transaction_detail}
如上便解決對於同一個key 獨佔訪問,但這僅限於同一個jvm,如果伺服器部署在叢集上便無法達到預期效果。
此時可以將每月總借款資訊記錄在一張表中,這樣在判斷與操作的時候可以根據這條記錄來判斷狀態
或者使用分布式的鎖,如:ZooKeeper進行管理。
Java業務原子性的一種實現(key 獨佔訪問)