线程调度、公平锁和非公平锁、乐观锁和悲观锁、锁优化、重入锁
1. 線程調度
線程調度指的就是給線程分配使用處理器的過程。主要的調度方式有兩種:協同式調度和搶占式調度。
1.1 協同式調度
線程完成自己的任務之后主動通知系統切換到另一個線程上。
優點:
實現簡單,線程對于自己的切換是已知的,不存在線程同步的問題;
缺點:
如果一個線程一直阻塞占用處理器,則其他線程都會被阻塞。
1.2 搶占式調度
由系統來為線程分配使用處理器的時間片。
優點:
線程的執行時間是系統可控的,不會出現一個線程導致整個進程阻塞的問題。
Java使用的線程調度就是搶占式調度。
2. 公平鎖與非公平鎖
公平鎖是多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來一次獲得鎖(默認的是不公平鎖);
非公平鎖就是一種獲取鎖的搶占機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖。
3. 樂觀鎖和悲觀鎖
3.1 悲觀鎖
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀。
每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。
傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
3.2 樂觀鎖
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀。
每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。
樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖。
樂觀鎖是基于沖突檢測的樂觀并發策略。
3.3 應用
Synchronized原始采用的就是CPU悲觀鎖機制,即線程獲取的是獨占鎖。
- 獨占鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖。
Lock采用的就是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止。樂觀鎖實現的機制就是CAS操作。
4. 鎖優化
Java虛擬機團隊在JDK1.5到JDK1.6之間對高效并發進行了重要改進,有以下五種技術,都是為了在線程之間更高效地共享數據,以及解決競爭問題,從而提高程序的執行效率。
這里的鎖優化主要是指 JVM 對 synchronized 的優化。
?4.1 自旋鎖和自適應鎖
互斥同步進入阻塞狀態的開銷都很大,應該盡量避免。
在許多應用中,共享數據的鎖定狀態只會持續很短的一段時間。
自旋鎖的思想是讓一個線程在請求一個共享數據的鎖時執行忙循環(自旋) 一段時間,如果在這段時間內能獲得鎖,就可以避免進入阻塞狀態。
自旋鎖雖然能避免進入阻塞狀態從而減少開銷,但是它需要進行忙循環操作占用CPU 時間,它只適用于共享數據的鎖定狀態很短的場景。
在 JDK 1.6 中引入了自適應的自旋鎖。自適應意味著自旋的次數不再固定了,而是由前一次在同一個鎖上的自旋次數及鎖的擁有者的狀態來決定。
自旋是有限定的次數的,在JDK 1.6引入了自適應自旋。
自適應意味著自旋的時間不再固定了,而是由前一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定。
4.2 鎖消除
鎖消除是指對于被檢測出不可能存在競爭的共享數據的鎖進行消除。
鎖消除主要是通過逃逸分析來支持,如果堆上的共享數據不可能逃逸出去被其它線程訪問到,那么就可以把它們當成私有數據對待,也就可以將它們的鎖進行消除。
對于一些看起來沒有加鎖的代碼,其實隱式的加了很多鎖。例如下面的字符串拼接代碼就隱式加了鎖:
public static String concatString(String s1, String s2, String s3) {return s1 + s2 + s3; }String 是一個不可變的類,編譯器會對 String 的拼接自動優化。在 JDK 1.5 之前,會轉化為 StringBuffer 對象的連續 append() 操作:
public static String concatString(String s1, String s2, String s3) {StringBuffer sb = new StringBuffer();sb.append(s1);sb.append(s2);sb.append(s3);return sb.toString(); }每個 append() 方法中都有一個同步塊。
虛擬機觀察變量 sb,很快就會發現它的動態作用域被限制在 concatString() 方法內部。
也就是說,sb 的所有引用永遠不會逃逸到 concatString() 方法之外,其他線程無法訪問到它,因此可以進行消除。
4.3 鎖粗化
如果一系列的連續操作都對同一個對象反復加鎖和解鎖,頻繁的加鎖操作就會導致性能損耗。
上一節的示例代碼中連續的 append() 方法就屬于這類情況。
如果虛擬機探測到由這樣的一串零碎的操作都對同一個對象加鎖,將會把加鎖的范圍擴展(粗化) 到整個操作序列的外部。
對于上一節的示例代碼就是擴展到第一個 append() 操作之前直至最后一個 append() 操作之后,這樣只需要加鎖一次就可以了。
JDK 1.6 引入了偏向鎖和輕量級鎖,從而讓鎖擁有了四個狀態:
- 無鎖狀態(unlocked)
- 偏向鎖狀態(biasble)
- 輕量級鎖狀態(lightweight locked)
- 重量級鎖狀態(inflated)
4.4 輕量級鎖
并不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下減少傳統重量級鎖使用操作系統互斥量產生的性能消耗。
輕量級鎖能提升程序同步性能的依據是“對于絕大部分的鎖,在整個同步周期內都是不存在競爭的”。
在無競爭的情況下使用CAS操作避免了使用互斥量的開銷,但是如果存在鎖競爭,除了互斥量的開銷外,還額外發生了CAS操作,因此在有競爭的情況下,輕量級鎖會比傳統的重量級鎖更慢。
適用場景:
追求響應時間,同步塊執行速度非常快。
使用操作系統互斥量來實現的傳統鎖稱為是“重量級”鎖。
4.5 偏向鎖
目的是消除數據在無競爭情況下的同步原語,進一步提高程序的運行性能。偏向鎖會偏向于第一個獲得它的線程,如果在后面的執行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步。
偏向鎖可以提高帶有同步但無競爭的程序性能。但是如果程序中大多數的鎖總是被多個不同的線程訪問,那偏向模式就是多余的。
適用場景:
只有一個線程訪問同步塊場景。
5. ReentrantLock(可重入鎖)
可重入鎖的三個特性:
5.1 等待可中斷
當持有鎖的線程長期不釋放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情。
可中斷特性對處理執行時間長的同步塊很有幫助。
5.2 公平鎖與非公平鎖
ReentrantLock默認是非公平鎖,但是可以通過帶布爾值的構造函數要求使用公平鎖。
公平鎖是多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來一次獲得鎖(默認的是不公平鎖);
非公平鎖就是一種獲取鎖的搶占機制,是隨機獲得鎖的,和公平鎖不一樣的就是先來的不一定先得到鎖。
5.3
鎖可以綁定多個條件condition,可以選擇性通知指定的某個種類的condition。
6.怎么檢查一個線程是否擁有鎖?
在java.lang.Thread中有一個方法叫holdsLock(),它返回true如果當且僅當當前線程擁有某個具體對象的鎖。
7. Lock與synchronized的區別
7.1 是否為語言內置:
synchronized是Java語言的關鍵字,因此是內置特性。
Lock不是Java語言內置的,是一個類,通過這個類可以實現同步訪問;
7.2 用法
synchronzied可以用于去修飾方法或代碼塊,而Lock則需要在代碼中顯示的通過調用lock()和unlock()方法來指定起始位置和終止位置。synchronize是托管給JVM的,而Lock的鎖定是通過代碼實現的,它有比synchronized更精確的線程語義。
7.3 鎖機制
Lock和synchronized有一點非常大的不同,
采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之后,系統會自動讓線程釋放對鎖的占用;
而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象;
7.4 并發量對性能的影響
并發數量多的時候synchronized性能會下降很厲害,但是對Lock影響不大。
7.5 Lock可以提高多個線程進行讀操作的效率。
?
總結
以上是生活随笔為你收集整理的线程调度、公平锁和非公平锁、乐观锁和悲观锁、锁优化、重入锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分页及其管理、页面置换算法
- 下一篇: 线程安全、守护线程、join()