在高負載多執行緒應用中性能是非常重要的,為了達到更好的性能,開發者必須意識到并發的重要性,當我們需要使用并發時, 常常有一個資源必須被兩個或多個執行緒共享,
在這種情況下,就存在一個競爭條件,也就是其中一個執行緒可以得到鎖(鎖與特定資源系結),其他想要得到鎖的執行緒會被阻塞,這個同步機制的實作是有代價的,為了向你提供一個好用的同步模型,JVM和作業系統都要消耗資源,有三個最重要的因素使并發的實作會消耗大量資源,它們是:
- 背景關系切換
- 記憶體同步
- 阻塞
為了寫出針對同步的優化代碼,你必須認識到這三個因素以及如何減少它們,在寫這樣的代碼時你需要注意很多東西,在本文中,我會向你介紹一種通過降低鎖粒度的技術來減少這些因素,
讓我們從一個基本原則開始:不要長時間持有不必要的鎖,
在獲得鎖之前做完所有需要做的事,只把鎖用在需要同步的資源上,用完之后立即釋放它,我們來看一個簡單的例子:
public class HelloSync {
private Map dictionary = new HashMap();
public synchronized void borringDeveloper(String key, String value) {
long startTime = (new java.util.Date()).getTime();
value = https://www.cnblogs.com/MonsterJ/p/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 = https://www.cnblogs.com/MonsterJ/p/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);
}
}
雜貨店主可以對他的雜貨鋪中的蔬菜和水果進行添加/洗掉操作,上面對雜貨鋪的實作,通過基本的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);
}
}
在使用了兩個鎖后(把鎖分離),我們會發現比起之前用一個整體鎖,鎖阻塞的情況更少了,當我們把這個技術用在有中度鎖爭搶的鎖上時,優化提升會更明顯,如果把該方法應用到輕微鎖爭搶的鎖上,改進雖然比較小,但還是有效果的,但是如果把它用在有重度鎖爭搶的鎖上時,你必須認識到結果并非總是更好,
請有選擇性的使用這個技術,如果你懷疑一個鎖是重度爭搶鎖請按下面的方法來確認是否使用上面的技術:
- 確認你的產品會有多少爭搶度,將這個爭搶度乘以三倍或五倍(甚至10倍,如果你想準備的萬無一失)
- 基于這個爭搶度做適當的測驗
- 比較兩種方案的測驗結果,然后挑選出最合適的.
用于改進同步性能的技識訓有很多,但對所有的技術來說最基本的原則只有一個:不要長時間持有不必要的鎖,
這潭訓本原則可以如我之前向你們解釋的那樣理解成“盡可能少的請求鎖”,也可以有其他解釋(實作方法),我將在之后的文章中進一步介紹,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/67399.html
標籤:Java
上一篇:幾種定時任務(Timer、TimerTask、ScheduledFuture)的退出—結合真實案例【JAVA并發】
