研究所學生階段一直使用C++,到工作時,才接觸到Java。寫了這麼多年的多線程程式,覺得對于互斥(注意,不是同步哦)的各種鎖有必要做個總結。
這裡我想将Windows,Linux和Java JVM三種環境中使用鎖的環境及雖然将Windows,Linux和Java JVM放在一起比較是有些不合适的,但是對基于Windows作業系統C++,Linux作業系統C++及Java程式而言,在應用層面上來說,這種橫向比較對于加深對于多線程互斥中鎖應用上的了解,是有意義的。如果有不正确的地方,歡迎指正。
Windows
有InterLock系列函數,CRITICAL_SECTION臨界區,SRWLock讀寫鎖,Mutex互斥量
Linux
有atomic_t,Mutex互斥量,,rwlock讀寫鎖
Java JVM
有Atomic原子操作,synchronized同步關鍵字,ReentrantLock,ReentrantReadWriteLock讀寫鎖
互斥量
環境
睡眠鎖 / 自旋鎖
可重入 / 不可重入
使用者态 / 核心态
可被中斷 / 不可被中斷
備注
CRITICAL_SECTION 臨界區
Windows
先自旋鎖,後睡眠鎖
可重入
使用者态
SRWLock 讀寫鎖
Windows
讀寫鎖
不可重入
使用者态
Mutex 互斥量
Windows
睡眠鎖
不可重入
核心态
spin_lock
Linux
自旋鎖
核心
Mutex 互斥量
Linux
睡眠鎖
pthread
rwlock
Linux
讀寫鎖
pthread
synchronized
JVM
類自旋鎖
可重入
JVM 内部鎖
ReentrantLock
JVM
類睡眠鎖
可重入
Java 類庫的鎖
ReentrantReadWriteLock
JVM
讀寫鎖
可重入
Java類庫的鎖
這裡着重說一下,何時使用自旋鎖,何時使用讀寫鎖,何時使用睡眠鎖。
在臨界區範圍較小,競争不強,并且其中沒有讀寫檔案之類的核心态操作時,最好使用自旋鎖;這是因為,自旋鎖是以一種“忙等”的方式搶占鎖,如果臨界區範圍較大,或競争很強,或者有核心态操作,導緻多個線程同時等待一個自旋鎖資源,伺服器的CPU使用率将會非常高,上下文切換會比較頻繁,性能不佳。對于Windows的臨界區,微軟特别做了優化,它會先自旋一段時間,然後,如果長時間進入不了臨界區,則會使用互斥量進入睡眠狀态。
在讀寫數量上差距很大時,(讀多寫少或者寫多讀少),使用讀寫鎖;讀寫鎖,大多數情況下都是自旋鎖的一個變種。
在臨界區範圍較大或者臨界區其中有核心态操作時,最好使用睡眠鎖;睡眠鎖在搶占不到鎖的情況下,會進入核心态,使自身所在的線程被挂起。在臨界區較小時,進入核心态的時間長度很可能大于在臨界區中的時間長度,導緻性能不佳,但是如果臨界區很大,其中操作時間長度大于睡眠鎖進入核心态的時間長度,尤其是在競争很強的情況下,性能相對自旋鎖要好些。
對于JVM,為什麼我會寫類自旋鎖和類睡眠鎖,這是因為,它們的性能情況和在C++下是不一樣的,在Java5.0中,synchronized的性能和ReentrantLock的性能的對比與自旋鎖和睡眠鎖的性能對比類似,但是在Java6.0中則不然,synchronized在競态的性能有了大幅的提高,使得在競态情況下,synchronized的性能和ReentrantLock的性能基本一緻。
最後,也是最重要的是,以上說法都不準确,唯一準确的是在你自己的代碼中測試性能的結果。