標籤:java並發編程
上一篇部落格中 : java多線程、並發系列之 (synchronized)同步與加鎖機制。介紹了java中Synchronized和簡單的加鎖機制,在加鎖的模組中介紹了 輪詢鎖和定時鎖,簡單回顧下
鎖的公平性
在公平的鎖上,線程將按照它們發出請求的順序來擷取鎖
上面似乎忘記了還有一種可中斷鎖和可選擇粒度鎖
可中斷鎖
正如定時的鎖擷取操作能在帶有時間限制的操作中使用獨佔鎖,可中斷的鎖擷取操作同樣能在可以取消的操作中使用加鎖。lockInterruptibly方法能夠在獲得鎖的同時保持對中斷的響應,並且由於它包含在Lock中,因此無須建立其他類型的不可中斷阻塞機制。
可中斷的鎖擷取操作的標準結構比普通的鎖擷取操作略複雜一些,,因為需要兩個try塊(如果在可中斷的鎖擷取操作中拋出InterruptedException,那麼可以使用標準的try-finally加鎖模式)。
如下程式中使用了lockInterruptibly來實現程式中的sendOnShareLine,以便在一個可取消的任務中調用他。
public boolean sendOnSharedLine(String message) throws InterruptedException{ lock.lockInterruptibly(); try { return cancellableSendOnSharedLine(message); }finally { lock.unlock(); } }private boolean cancellableSendOnSharedLine(String message) throws InterruptedException{...}
效能考慮(內建鎖和顯示鎖)
在JDK1.5中,synchronized是效能低效的。因為這是一個重量級操作,它對效能最大的影響是阻塞的是實現,掛起線程和恢複線程的操作都需要轉入核心態中完成,這些操作給系統的並發性帶來了很大的壓力。相比之下使用Java提供的Lock對象,效能更高一些。Brian Goetz對這兩種鎖在JDK1.5、單核處理器及雙Xeon處理器環境下做了一組輸送量對比的實驗,發現多線程環境下,synchronized的輸送量下降的非常嚴重,而ReentrankLock則能基本保持在同一個比較穩定的水平上。但與其說ReetrantLock效能好,倒不如說synchronized還有非常大的最佳化餘地,於是到了JDK1.6,發生了變化,對synchronize加入了很多最佳化措施,有自適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK1.6上synchronize的效能並不比Lock差。官方也表示,他們也更支援synchronize,在未來的版本中還有最佳化餘地,所以還是提倡在synchronized能實現需求的情況下,優先考慮使用synchronized來進行同步。
二者的效能比較如下:圖來自java並發編程實戰
鎖的公平性考慮
ReentrantLock的建構函式中提供了兩種公平性選擇:建立一個非公平的鎖或者一個公平的鎖。
公平的鎖
線程將按照它們發出請求的順序來擷取鎖,新發出的請求線程將被放入等待隊列中。
非公平的鎖
在公平鎖的基礎上面允許插隊,當一個線程擷取鎖時,如果在發出請求的同時,該鎖的狀態變為可用,那麼這個線程將跳過隊列中的所有等待線程並獲得鎖。
我們並不希望所有的鎖都是公平的。雖然公平是一種好的行為,但是對於系統的效率來說並不總是好的。看這樣一個例子
等待隊列 對頭 1-> 2 ->3 ->4 隊尾 有四個等待線程,線程0正好執行結束,此時一個線程5來了,按照公平鎖的方法,那麼5將會進入等待隊列 1-> 2 ->3 ->4 -> 5 ,然後再從對頭中取出線程1, 但是如果,採用非公平的方法,線上程5來了的同時,將線程5直接執行,跳過等待隊列,那麼就會減少掛起線程5和恢複線程1的開銷,輸送量獲得了提升
在激烈競爭的環境中,非公平鎖的效能高於公平鎖的效能的一個原因是:在恢複一個被掛起線程與該線程真正開始執行之間存在著嚴重的延遲。
兩種鎖的效能比較如,圖來自java並發編程實戰
在Synchronized和ReentrantLock之間進行選擇
由上面可知ReentrantLock在加鎖和記憶體提供的語義上面與內建鎖相同,此外它還提供了一些其他的功能,包括定時的鎖等待、可中斷的鎖等待、公平性,以及實現非塊結構的加鎖,ReentrantLock在效能上似乎優於內建鎖。但是,內建鎖在一些場合中仍然具有很大的優勢
- 內建鎖為許多開發人員熟悉,並且簡潔緊湊,而且在許多現有的程式中,如果兩種機制混合使用,那麼不僅僅容易令人困惑,也容易發生錯誤
- ReentrantLock的危險性比同步機制要高,如果忘記在finally塊中調用unlock,那麼雖然代碼錶面能夠運行,但是實際上已經埋上了一顆定時炸彈,並且很有可能危機其他的代碼。
什麼時候用ReentrantLock:僅當內建鎖不能夠滿足需求時,才考慮使用ReentrantLock,這些需求包括:可定時、可輪詢的與可中斷的鎖擷取,公平隊列,以及非塊結構的鎖
java多線程並發系列之鎖的深入瞭解