在這種情況下,我們有一個競爭條件 ,其中只有一個線程(在資源上)將獲得鎖,而所有其他需要該鎖的線程將被阻塞。 此同步功能不是免費提供的。 JVM和OS都會消耗資源,以便為您提供有效的并發模型。 使并發實現資源密集的三個最基本的因素是:
- 上下文切換
- 內存同步
- 封鎖
為了編寫優化的代碼進行同步,您必須了解這三個因素以及如何減少它們。 編寫此類代碼時,必須注意許多事項。 在本文中,我將向您展示一種通過減少鎖的粒度來減少這些因素的技術。
從基本規則開始: 不要將鎖持有超過必要的時間。
獲取鎖之前 ,請做任何您需要做的事情, 僅將鎖用于對同步資源進行操作并立即釋放它。 看一個簡單的例子:
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");}
}
在此示例中,我們違反了基本規則,因為我們創建了兩個Date對象,調用System.out.println()并執行許多String串聯。 唯一需要同步的動作是動作:“ dictionary.put(key,value); ”更改代碼并將同步從方法范圍移到這一行。 更好的代碼是這樣的:
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");}
}
上面的代碼可以寫得更好,但是我只想給你個想法。 如果想知道如何做,請檢查java.util.concurrent.ConcurrentHashMap 。
那么,如何減少鎖的粒度呢? 簡而言之,通過盡可能少地請求鎖。 基本思想是使用單獨的鎖來保護一個類的多個獨立狀態變量,而不是在類范圍內僅具有一個鎖。 看看我在許多應用程序中看到的這個簡單示例。
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);}
}
雜貨店老板可以在他的雜貨店添加水果和蔬菜/從中刪除水果和蔬菜。 雜貨店的這種實現使用基本的雜貨店鎖來保護水果和蔬菜,因為同步是在方法范圍內完成的。 除了使用這種胖鎖,我們可以使用兩個單獨的守護程序,每種資源(水果和蔬菜)使用一個。 檢查下面的改進代碼。
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);}
}
使用兩個防護裝置(拆分鎖)后,我們看到的鎖定流量將少于原始的胖鎖。 當我們將其應用于具有中等鎖爭用的鎖時,此技術會更好地工作。 如果我們將其應用于具有輕微爭用的鎖,則收益很小,但仍為正值。 如果我們將其應用于爭用較大的鎖,則結果并不總是更好,您必須意識到這一點。
請出于良心使用此技術。 如果您懷疑這是一個爭用鎖,請按照以下步驟操作:
- 確認您的生產需求量,將其乘以3或5(即使您愿意準備,甚至乘以10)。
- 根據新流量在您的測試平臺上運行適當的測試。
- 比較兩個解決方案,然后才選擇最合適的解決方案。
有更多技術可以提高同步性能,但是對于所有技術,基本規則是: 保持鎖的時間不要超過必要的時間 。
正如我已經向您解釋的那樣,此基本規則可以翻譯為“盡可能少地尋求鎖”,也可以翻譯為其他翻譯(解決方案),我將在以后的文章中嘗試描述它們。
另外兩個重要的建議:
- 請注意java.util.concurrent包(和子包) 中的類,因為它們有非常聰明和有用的實現。
- 通過使用良好的設計模式,大多數時候并發代碼可以最小化。 始終牢記企業集成模式 ,它們可以節省您的時間。
參考: 減少鎖粒度–我們Java的 JCG合作伙伴 Adrianos Dadis的并發優化 ,Integration and the sources 的優點 。
- Java并發教程–信號量
- Java并發教程–重入鎖
- Java并發教程–線程池
- Java并發教程–可調用,將來
- Java并發教程–阻塞隊列
- Java并發教程– CountDownLatch
- Java Fork / Join進行并行編程
- Java內存模型–快速概述和注意事項
- Java教程和Android教程列表
翻譯自: https://www.javacodegeeks.com/2011/10/concurrency-optimization-reduce-lock.html