併發編程中是如何下降鎖粒度的,怎麼作到性能優化!

在高負載多線程應用中性能是很是重要的。爲了達到更好的性能,開發者必須意識到併發的重要性。當咱們須要使用併發時, 經常有一個資源必須被兩個或多個線程共享。java

在這種狀況下,就存在一個競爭條件,也就是其中一個線程能夠獲得鎖(鎖與特定資源綁定),其餘想要獲得鎖的線程會被阻塞。這個同步機制的實現是有代價的,爲了向你提供一個好用的同步模型,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 = 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.ui

那麼,咱們怎麼下降鎖粒度呢?簡單來講,就是經過儘量少的請求鎖。基本的想法是,分別用不一樣的鎖來保護同一個類中多個獨立的狀態變量,而不是對整個類域只使用一個鎖。咱們來看下面這個我在不少應用中見到過的簡單例子:this

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)分別使用一個鎖。來看一下改進後的代碼:spa

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倍,若是你想準備的萬無一失)
  • 基於這個爭搶度作適當的測試
  • 比較兩種方案的測試結果,而後挑選出最合適的.

用於改進同步性能的技術還有不少,但對全部的技術來講最基本的原則只有一個:不要長時間持有沒必要要的鎖。

這條基本原則能夠如我以前向大家解釋的那樣理解成「儘量少的請求鎖」,也能夠有其餘解釋(實現方法),我將在以後的文章中進一步介紹。

file

相關文章
相關標籤/搜索