Java并發(fā)編程如何降低鎖粒度并實現(xiàn)性能優(yōu)化
在高負載多線程應(yīng)用中性能是非常重要的。為了達到更好的性能,開發(fā)者必須意識到并發(fā)的重要性。當我們需要使用并發(fā)時, 常常有一個資源必須被兩個或多個線程共享。
在這種情況下,就存在一個競爭條件,也就是其中一個線程可以得到鎖(鎖與特定資源綁定),其他想要得到鎖的線程會被阻塞。這個同步機制的實現(xiàn)是有代價的,為了向你提供一個好用的同步模型,JVM和操作系統(tǒng)都要消耗資源。有三個最重要的因素使并發(fā)的實現(xiàn)會消耗大量資源,它們是:
- 上下文切換
- 內(nèi)存同步
- 阻塞
為了寫出針對同步的優(yōu)化代碼,你必須認識到這三個因素以及如何減少它們。在寫這樣的代碼時你需要注意很多東西。在本文中,我會向你介紹一種通過降低鎖粒度的技術(shù)來減少這些因素。
讓我們從一個基本原則開始:不要長時間持有不必要的鎖。
在獲得鎖之前做完所有需要做的事,只把鎖用在需要同步的資源上,用完之后立即釋放它。我們來看一個簡單的例子:
public class HelloSync { private Map dictionary = new HashMap(); public synchronized void borringDeveloper(String key, String value) { long startTime = (new java.util.Date()).getTime(); value = value + "_"+startTime; dictionary.put(key, value); System.out.println("I did this in "+ ((new java.util.Date()).getTime() - startTime)+" miliseconds"); } }
在這個例子中,我們違反了基本原則,因為我們創(chuàng)建了兩個Date對象,調(diào)用了System.out.println(),還做了很多次String連接操作,但唯一需要做同步的操作是“dictionary.put(key, value);”。讓我們來修改代碼,把同步方法變成只包含這句的同步塊,得到下面更優(yōu)化的代碼:
public class HelloSync { private Map dictionary = new HashMap(); public void borringDeveloper(String key, String value) { long startTime = (new java.util.Date()).getTime(); value = value + "_"+startTime; synchronized (dictionary) { dictionary.put(key, value); } System.out.println("I did this in "+ ((new java.util.Date()).getTime() - startTime)+" miliseconds"); } }
上面的代碼可以進一步優(yōu)化,但這里只想傳達出這種想法。如果你對如何進一步優(yōu)化感興趣,請參考java.util.concurrent.ConcurrentHashMap.
那么,我們怎么降低鎖粒度呢?簡單來說,就是通過盡可能少的請求鎖?;镜南敕ㄊ牵謩e用不同的鎖來保護同一個類中多個獨立的狀態(tài)變量,而不是對整個類域只使用一個鎖。我們來看下面這個我在很多應(yīng)用中見到過的簡單例子:
public class Grocery { private final ArrayList fruits = new ArrayList(); private final ArrayList vegetables = new ArrayList(); public synchronized void addFruit(int index, String fruit) { fruits.add(index, fruit); } public synchronized void removeFruit(int index) { fruits.remove(index); } public synchronized void addVegetable(int index, String vegetable) { vegetables.add(index, vegetable); } public synchronized void removeVegetable(int index) { vegetables.remove(index); } }
雜貨店主可以對他的雜貨鋪中的蔬菜和水果進行添加/刪除操作。上面對雜貨鋪的實現(xiàn),通過基本的Grocery 鎖來保護fruits和vegetables,因為同步是在方法域完成的。事實上,我們可以不使用這個大范圍的鎖,而是針對每個資源(fruits和vegetables)分別使用一個鎖。來看一下改進后的代碼:
public class Grocery { private final ArrayList fruits = new ArrayList(); private final ArrayList vegetables = new ArrayList(); public void addFruit(int index, String fruit) { synchronized(fruits) fruits.add(index, fruit); } public void removeFruit(int index) { synchronized(fruits) {fruits.remove(index);} } public void addVegetable(int index, String vegetable) { synchronized(vegetables) vegetables.add(index, vegetable); } public void removeVegetable(int index) { synchronized(vegetables) vegetables.remove(index); } }
在使用了兩個鎖后(把鎖分離),我們會發(fā)現(xiàn)比起之前用一個整體鎖,鎖阻塞的情況更少了。當我們把這個技術(shù)用在有中度鎖爭搶的鎖上時,優(yōu)化提升會更明顯。如果把該方法應(yīng)用到輕微鎖爭搶的鎖上,改進雖然比較小,但還是有效果的。但是如果把它用在有重度鎖爭搶的鎖上時,你必須認識到結(jié)果并非總是更好。
請有選擇性的使用這個技術(shù)。如果你懷疑一個鎖是重度爭搶鎖請按下面的方法來確認是否使用上面的技術(shù):
- 確認你的產(chǎn)品會有多少爭搶度,將這個爭搶度乘以三倍或五倍(甚至10倍,如果你想準備的萬無一失)
- 基于這個爭搶度做適當?shù)臏y試
- 比較兩種方案的測試結(jié)果,然后挑選出最合適的.
用于改進同步性能的技術(shù)還有很多,但對所有的技術(shù)來說最基本的原則只有一個:不要長時間持有不必要的鎖。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
spring-boot實現(xiàn)增加自定義filter(新)
本篇文章主要介紹了spring-boot實現(xiàn)增加自定義filter(新),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-05-05Spring?Cloud實現(xiàn)灰度發(fā)布的示例代碼
這篇文章主要為大家詳細介紹了Spring?Cloud實現(xiàn)灰度發(fā)布的相關(guān)知識,文中的示例代碼講解詳細,具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解一下2023-09-09SpringBoot基于SpringSecurity表單登錄和權(quán)限驗證的示例
這篇文章主要介紹了SpringBoot基于SpringSecurity表單登錄和權(quán)限驗證的示例。文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Mybatis流式查詢并實現(xiàn)將結(jié)果分批寫入文件
這篇文章主要介紹了Mybatis流式查詢并實現(xiàn)將結(jié)果分批寫入文件方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08詳解SpringBoot中@ConditionalOnClass注解的使用
這篇文章主要和大家詳細介紹一下springboot中@ConditionalOnClass注解的用法,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2022-08-08