怎麼正確使用鎖?

鎖的原理:任何時間都只能有一個線程持有鎖,只有持有鎖的線程才能訪問被鎖保護的資源。

我們接下來看一下在鎖的使用上有什麼最佳實踐。

避免濫用鎖

如果能不用鎖,就不用鎖;如果你不確定是不是應該用鎖,那也不要鎖。

使用鎖後帶來的代價:

  1. 加鎖和解鎖過程都需要CPU時間的,這是一個性能的損失。使用鎖還可能導致線程等待鎖,等待鎖過程中的線程是阻塞狀態,過多的鎖等待會顯著降低程序的性能。
  2. 如果對鎖使用不當,很容易造成死鎖,導致整個程序“卡死”,這是非常嚴重的問題。

我們不可以看到一個共享數據,在沒有搞清楚它在併發環境中是否會出現爭用問題,就“爲了保險,給它加個鎖吧。”,千萬不要有這種不負責任的想法,否則你將會付出慘痛的代價。

只有在併發環境中,共享資源不支持併發訪問,或者說併發訪問共享資源會導致系統錯誤的情況下,才需要使用鎖。

鎖的用法

使用鎖的過程可以分爲三步:

  1. 在訪問共享資源之前,先獲取鎖。
  2. 如果獲取鎖成功,就可以訪問共享資源了。
  3. 使用完共享資源後釋放鎖,以便其他線程繼續訪問共享資源。

我們在使用鎖的過程中,需要注意使用完鎖,一定要釋放它。我們需要考慮到代碼可能走到的所有正常和異常的分支,確保所有情況下,鎖都能被釋放。

死鎖

死鎖是指由於某種原因,鎖一直沒有釋放,後續需要獲取鎖的線程都將處於等解鎖狀態。

大部分編程語言都提供了可重入鎖,如果沒有特別的需求,我們也要儘量使用可重入鎖。

下面是幾條如何避免死鎖的建議:

  1. 避免濫用鎖。
  2. 對於同一把鎖,加鎖和解鎖必須要放在同一個方法中,這樣一次加鎖對應一次解鎖,代碼清晰簡單,便於分析問題。
  3. 儘量避免在持有一把鎖的情況下,去獲取另外一把鎖,就是要儘量避免同時持有多把鎖。
  4. 如果需要持有多把鎖,一定要注意加解鎖的順序,解鎖的順序要和加鎖的殊勳想法,比如,獲取三把鎖的順序是A、B、C,釋放鎖的順序必須是C、B、A。

使用讀寫鎖兼顧性能和安全

對於共享數據,如果我們的方法只是去讀取它,而不會修改,也是需要加鎖的,因爲有可能在讀取數據的過程中,有其他線程會更新數據。

但如果只是簡單地爲數據加一個鎖,對於“讀多寫少”的場景,性能會受到影響。針對數據的讀寫操作,我們希望能夠做到:1)讀操作可以併發執行,2)寫的同時不能併發讀,也不能併發寫。

Java中的ReadWriteLock可以用來解決這個問題,看下面的代碼框架:


ReadWriteLock rwlock = new ReentrantReadWriteLock();

public void read() {
  rwlock.readLock().lock();
  try {
    // 在這兒讀取共享數據
  } finally {
    rwlock.readLock().unlock();
  }
}
public void write() {
  rwlock.writeLock().lock();
  try {
    // 在這兒更新共享數據
  } finally {
    rwlock.writeLock().unlock();
  }
}

在這段代碼中,需要讀數據的時候,我們獲取鎖,這個鎖不是一個互斥鎖,即read()方法可以支持多個線程並行執行,從而保證數據的讀性能。寫數據的時候,我們獲得寫鎖,這是一個互斥鎖,當一個線程持有寫鎖的時候,其他線程既無法獲得讀鎖,也無法獲得寫鎖,從而達到了保護數據的目的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章