這篇文章主要關註分散式鎖,包括加鎖和解鎖的過程,鎖的用法,加鎖帶來的代價,對性能的影響以及如何避免死鎖。 ...
鎖的原理:任何時間都只能有一個線程持有鎖,只有持有鎖的線程才能訪問被鎖保護的資源。
我們接下來看一下在鎖的使用上有什麼最佳實踐。
避免濫用鎖
如果能不用鎖,就不用鎖;如果你不確定是不是應該用鎖,那也不要鎖。
使用鎖後帶來的代價:
- 加鎖和解鎖過程都需要CPU時間的,這是一個性能的損失。使用鎖還可能導致線程等待鎖,等待鎖過程中的線程是阻塞狀態,過多的鎖等待會顯著降低程式的性能。
- 如果對鎖使用不當,很容易造成死鎖,導致整個程式“卡死”,這是非常嚴重的問題。
我們不可以看到一個共用數據,在沒有搞清楚它在併發環境中是否會出現爭用問題,就“為了保險,給它加個鎖吧。”,千萬不要有這種不負責任的想法,否則你將會付出慘痛的代價。
只有在併發環境中,共用資源不支持併發訪問,或者說併發訪問共用資源會導致系統錯誤的情況下,才需要使用鎖。
鎖的用法
使用鎖的過程可以分為三步:
- 在訪問共用資源之前,先獲取鎖。
- 如果獲取鎖成功,就可以訪問共用資源了。
- 使用完共用資源後釋放鎖,以便其他線程繼續訪問共用資源。
我們在使用鎖的過程中,需要註意使用完鎖,一定要釋放它。我們需要考慮到代碼可能走到的所有正常和異常的分支,確保所有情況下,鎖都能被釋放。
死鎖
死鎖是指由於某種原因,鎖一直沒有釋放,後續需要獲取鎖的線程都將處於等解鎖狀態。
大部分編程語言都提供了可重入鎖,如果沒有特別的需求,我們也要儘量使用可重入鎖。
下麵是幾條如何避免死鎖的建議:
- 避免濫用鎖。
- 對於同一把鎖,加鎖和解鎖必須要放在同一個方法中,這樣一次加鎖對應一次解鎖,代碼清晰簡單,便於分析問題。
- 儘量避免在持有一把鎖的情況下,去獲取另外一把鎖,就是要儘量避免同時持有多把鎖。
- 如果需要持有多把鎖,一定要註意加解鎖的順序,解鎖的順序要和加鎖的殊勛想法,比如,獲取三把鎖的順序是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()方法可以支持多個線程並行執行,從而保證數據的讀性能。寫數據的時候,我們獲得寫鎖,這是一個互斥鎖,當一個線程持有寫鎖的時候,其他線程既無法獲得讀鎖,也無法獲得寫鎖,從而達到了保護數據的目的。
作者:李潘 出處:http://wing011203.cnblogs.com/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。