提交訂單效能優化系列之009-對比整個方法同步與方法中的部分程式碼同步
概括總結
在用到synchronized
關鍵字的時候,憑直覺就會加在方法上,比如public static synchronized void test(){}
,但是這種直覺不見得是對的,估計大部分時候是出圖方便,想偷懶,才直接加到方法上的。推薦的做法是:僅僅只同步需要同步的程式碼。
009版本更新說明
這個版本主要是對比以下兩種程式碼的效能:(程式碼為虛擬碼)
第一種:整個方法同步
public synchronized void submitOrderSynchronized(省略引數) { // 1、查詢使用者 // 2、查詢商品 // 3、查詢地址 // 4、檢查相關的引數是否正確 // 5、儲存訂單到資料庫中,並返回訂單ID // 6、儲存訂單商品到資料庫中 // 7、更新商品的庫存與銷量 }
第二種:方法中的部分程式碼同步
public void submitOrder(省略引數) {
// 1、查詢使用者
synchronized (this) {
// 2、查詢商品
// 4、檢查相關的引數是否正確
// 7、更新商品的庫存與銷量
}
// 3、查詢地址
// 5、儲存訂單到資料庫中,並返回訂單ID
// 6、儲存訂單商品到資料庫中
}
測試結果
統計10次測試的平均值之後:
呼叫submitOrderSynchronized
類中的提交訂單方法時,每秒鐘可以提交的訂單數為:9
呼叫submitOrder
類中的提交訂單方法時,每秒鐘可以提交的訂單數為:14
呼叫submitOrderSynchronized
類中的提交訂單方法時,提交每個訂單平均耗時的納秒數:104506373
呼叫submitOrder
類中的提交訂單方法時,提交每個訂單平均耗時的納秒數:65949157
效能差別為:(104506373 - 65949157) / 104506373 * 100% = 36.89%,即第二種方法比第一種方法的效能好出36.89%
【備註】:不同的機器上的測試結果會不一樣,以上測試結果僅供參考。
測試結果說明
首先要說明一個問題:為什麼提交訂單時需要同步?答案是:為了保證庫存不會小於零。
在程式碼的第4步(檢查相關的引數是否正確)中,會檢查庫存是否正確,如果庫存已經為零,則不允許下單。這段程式碼的業務邏輯本身沒有問題,但是如果沒有同步,在多執行緒訪問時,會出現以下情況:
執行緒A查詢出商品,庫存為1
執行緒B查詢出商品,庫存為1
執行緒A檢查庫存,發現大於零,於是提交訂單,資料庫中的庫存變為0
執行緒B檢查庫存,發現大於零,於是提交訂單,資料庫中的庫存變為-1
如果加上同步,則在多執行緒訪問時,情況會變成這樣:
執行緒A查詢出商品,庫存為1
執行緒A檢查庫存,發現大於零,於是提交訂單,資料庫中的庫存變為0
執行緒B查詢出商品,庫存為0
執行緒B檢查庫存,發現等於零,不會提交訂單
所以同步在這裡起到的作用是:保證執行緒按我們期待的順序來執行。
同步會降低效能,同步的範圍越大,效能下降得越狠。第一種方案在方法上直接加同步,意味著方法中的7步操作都必須按順序執行。而第二種方案中,只對7步中的其中3步加了同步,其他的4步可以併發執行,因此效能自然會好一些。
但是值得一提的是,這個例子僅僅只是為了說明整個方法同步
與方法中的部分程式碼同步
的區別,並沒有完全按照概括總結中推薦的做法僅僅只同步需要同步的程式碼
來做,需要後期來完善。