天天看點

java多線程:synchronized和lock比較淺析

轉載:http://www.toutiao.com/a6392135944652587266/?tt_from=weixin&utm_campaign=client_share&app=news_article&utm_source=weixin&iid=7704173001&utm_medium=toutiao_ios&wxshare_count=1

synchronized是基于jvm底層實作的資料同步,lock是基于Java編寫,主要通過硬體依賴CPU指令實作資料同步。下面一一介紹

一、synchronized的實作方案

a.當synchronized作用于非靜态方法時,鎖住的是目前對象的事例,當synchronized作用于靜态方法時,鎖住的是class執行個體,又因為Class的相關資料存儲在永久帶,是以靜态方法鎖相當于類的一個全局鎖。

b.當synchronized作用于一個對象執行個體時,鎖住的是對應的代碼塊。

>Contention List:競争隊列,所有請求鎖的線程首先被放在這個競争隊列中

>Entry List:Contention List中那些有資格成為候選資源的線程被移動到Entry List中

>Wait Set:哪些調用wait方法被阻塞的線程被放置在這裡

>OnDeck:任意時刻,最多隻有一個線程正在競争鎖資源,該線程被成為OnDeck

>Owner:目前已經擷取到所資源的線程被稱為Owner

> !Owner:目前釋放鎖的線程

下圖展示了他們之前的關系

java多線程:synchronized和lock比較淺析

>自旋鎖

jdk1.6 之後預設開啟,可以使用參數-XX:+UseSpinning控制,自旋等待不能代替阻塞,且先不說對處理器數量的要求,自旋等待本身雖然避免了線程切換 的開銷,但它是要占用處理器時間的,是以,如果鎖被占用的時間很短,自旋等待的效果就會非常好,反之,如果鎖被占用的時候很長,那麼自旋的線程隻會白白消 耗處理器資源,而不會做任何有用的工作,反而會帶來性能上的浪費。自旋次數的預設值是 10 次,使用者可以使用參數 -XX:PreBlockSpin 來更改。

自旋鎖的本質:執行幾個空方法,稍微等一等,也許是一段時間的循環,也許是幾行空的彙編指令。

>鎖消除

即時編譯器在運作時,對一些代碼上要求同步,但是被檢測到不可能存在共享資料競争的鎖進行消除,依據來源于逃逸分析的資料支援,那麼是什麼是逃逸分析?對于虛拟機來說需要使用資料流分析來确定是否消除變量底層架構的同步代碼,因為有許多同步的代碼不是自己寫的。

例1.1

由于 String 是一個不可變的類,對字元串的連接配接操作總是通過生成新的 String 對象來進行的,是以 Javac 編譯器會對 String 連接配接做自動優化。在 JDK 1.5 之前,會轉化為 StringBuffer 對象的連續 append 操作,在 JDK 1.5 及以後的版本中,會轉化為 StringBuilder 對象的連續 append 操作,這裡的stringBuilder.append是線程不同步的(假設是同步)。

Javac 轉化後的字元串連接配接代碼為:

此時的鎖對象就是sb,虛拟機觀察變量 sb,很快就會發現它的動态作用域被限制在 concatString 方法内部。也就是說,sb 的所有引用永遠不會 “逃逸” 到concatString 方法之外,其他線程無法通路到它,雖然這裡有鎖,但是可以被安全地消除掉,在即時編譯之後,這段代碼就會忽略掉所有的同步而直接執行了。

>鎖粗化

将同步塊的作用範圍限制得盡量小——隻在共享資料的實際作用域中才進行同步,這樣是為了使得需要同步的操作數量盡可能變小,如果存在鎖競争,那等待鎖的線程也能盡快拿到鎖。

>輕量級鎖

加 鎖過程:在代碼進入同步塊的時候,如果此同步對象沒有被鎖定(鎖标志位為 “01” 狀态)虛拟機首先将在目前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的 Mark Word 的拷貝,這時候線程堆棧與對象頭的狀态如圖 13-3 所示

java多線程:synchronized和lock比較淺析

然後,虛拟機将使用 CAS 操作嘗試将對象的 Mark Word 更新為指向 Lock Record 的指針。如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,并且對象 Mark Word 的鎖标志位 (Mark Word 的最後 2bit)将轉變為 “00”,即表示此對象處于輕量級鎖定狀态,這時線程堆棧與對象頭的狀态如圖13-4

java多線程:synchronized和lock比較淺析

如果上述更新操作失敗,則說明這個鎖對象被其他鎖占用,此時輕量級變為重量級鎖,标志位為“10”,後面等待的線程進入阻塞狀态。

解 鎖過程:也是由CAS進行操作的,如果對象的 Mark Word 仍然指向着線程的鎖記錄,那就用 CAS 操作把對象目前的 Mark Word 和線程中複制的 Displaced Mark Word 替換回來,如果替換成功,整個同步過程就完成了。如果替換失敗,說明有其他線程嘗試過擷取該鎖,那就要釋放鎖的同時,喚醒被挂起的線程。

輕 量級鎖能提升程式同步性能的依據是 “對于絕大部分的鎖,在整個同步周期内都是不存在競争的”,這是一個經驗資料。如果沒有競争,輕量級鎖使用 CAS 操作避免了使用互斥量的開銷,但如果存在鎖競争,除了互斥量的開銷外,還額外發生了 CAS 操作,是以在有競争的情況下,輕量級鎖會比傳統的重量級鎖更慢。

>偏向鎖

偏向鎖也 是 JDK 1.6 中引入的一項鎖優化,它的目的是消除資料在無競争情況下的同步原語,進一步提高程式的運作性能。如果說輕量級鎖是在無競争的情況下使用 CAS 操作去消除同步使用的互斥量,那偏向鎖就是在無競争的情況下把整個同步都消除掉,連 CAS 操作都不做了。

實質就是設定一個變量,判斷這 個變量是否是目前線程,是就避免再次加鎖解鎖操作,進而避免了多次的CAS操作。壞處是如果一個線程持有偏向鎖,另外一個線程想争用偏向對象,擁有者想釋 放這個偏向鎖,釋放會帶來額外的性能開銷,但是總體來說偏向鎖帶來的好處還是大于CAS的代價的。在具體問題具體分析的前提下,有時候使用參數 -XX:-UseBiasedLocking 來禁止偏向鎖優化反而可以提升性能。

二、lock的實作方案

與 synchronized不同的是lock是純java手寫的,與底層的JVM無關。在java.util.concurrent.locks包中有很多 Lock的實作類,常用的有ReenTrantLock、ReadWriteLock(實作類有ReenTrantReadWriteLock)

,其實作都依賴java.util.concurrent.AbstractQueuedSynchronizer類(簡稱AQS),實作思路都大同小異,是以我們以ReentrantLock作為講解切入點。

分 析之前我們先來花點時間看下AQS。AQS是我們後面将要提到的CountDownLatch/FutureTask/ReentrantLock /RenntrantReadWriteLock/Semaphore的基礎,是以AQS也是Lock和Excutor實作的基礎。它的基本思想就是一個 同步器,支援擷取鎖和釋放鎖兩個操作。

java多線程:synchronized和lock比較淺析

要支援上面鎖擷取、釋放鎖就必須滿足下面的條件:

1、 狀态位必須是原子操作的

2、 阻塞和喚醒線程

3、 一個有序的隊列,用于支援鎖的公平性

場景:可定時的、可輪詢的與可中斷的鎖擷取操作,公平隊列,或者非塊結構的鎖。

主要從以下幾個特點介紹:

1.可重入鎖

如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了鎖的配置設定機制:基于線程的配置設定,而不是基于方法調用的配置設定。

2.可中斷鎖

可中斷鎖:顧名思義,就是可以相應中斷的鎖。

在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

如果某一線程A正在執行鎖中的代碼,另一線程B正在等待擷取該鎖,可能由于等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在别的線程中中斷它,這種就是可中斷鎖。

3.公平鎖和非公平鎖

公 平鎖以請求鎖的順序來擷取鎖,非公平鎖則是無法保證按照請求的順序執行。synchronized就是非公平鎖,它無法保證等待的線程擷取鎖的順序。而對 于ReentrantLock和ReentrantReadWriteLock,它預設情況下是非公平鎖,但是可以設定為公平鎖。

參數為true時表示公平鎖,不傳或者false都是為非公平鎖。

4.讀寫鎖

讀寫鎖将對一個資源(比如檔案)的通路分成了2個鎖,一個讀鎖和一個寫鎖。

正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生沖突。

ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實作了這個接口。

可以通過readLock擷取讀鎖,通過writeLock擷取寫鎖。

三、總結

1.synchronized

優點:實作簡單,語義清晰,便于JVM堆棧跟蹤,加鎖解鎖過程由JVM自動控制,提供了多種優化方案,使用更廣泛

缺點:悲觀的排他鎖,不能進行進階功能

2.lock

優點:可定時的、可輪詢的與可中斷的鎖擷取操作,提供了讀寫鎖、公平鎖和非公平鎖

缺點:需手動釋放鎖unlock,不适合JVM進行堆棧跟蹤

3.相同點

都是可重入鎖

參考文章:http://www.cnblogs.com/longshiyVip/p/5213771.html