无招胜有招之锁
一、CAS、樂觀鎖與悲觀鎖、數據庫相關鎖機制、分布式鎖、偏向鎖、輕量級鎖、重量級鎖、Monitor
CAS:在java并發應用中通常指CompareAndSwap或CompareAndSet,即比較并交換。
簡單來說就是一種無鎖算法,有三個操作數(內存值V,舊的預期值A,要修改的新值B。當且僅當A=V,將V修改為B否則什么都不做。)
樂觀鎖與悲觀鎖:
1.樂觀鎖:總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖。但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。
2.悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據時都會上鎖,這樣別人想拿這個數據時就會阻塞,直到他拿到鎖(共享資源每次只給一個線程使用,用完后再把資源轉讓給其他線程。)
數據庫相關鎖機制:
數據庫鎖一般分為兩類,一個是悲觀鎖,一個是樂觀鎖。
樂觀鎖一般是指用戶自己實現的一種鎖機制,悲觀鎖一般就是我們通常說的數據庫鎖機制,悲觀鎖主要表鎖、行鎖、頁鎖。
分布式鎖:
分布式鎖是控制分布式系統之間同步訪問共享資源的一種方式。
分布式鎖應該具備哪些條件:
2、高可用的獲取鎖與釋放鎖;?
3、高性能的獲取鎖與釋放鎖;?
4、具備可重入特性;?
5、具備鎖失效機制,防止死鎖;?
6、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗。
分布式鎖的三種實現方式:
2.基于緩存(Redis等)實現分布式鎖;?
3.基于Zookeeper實現分布式鎖;
偏向鎖:
在沒有實際競爭的情況下,還能夠針對部分場景繼續優化。如果不僅僅沒有實際競爭,自始至終,使用鎖的線程都只有一個,那么,維護輕量級鎖都是浪費的。偏向鎖的目標是,減少無競爭且只有一個線程使用鎖的情況下,使用輕量級鎖產生的性能消耗。輕量級鎖每次申請、釋放鎖都至少需要一次CAS,但偏向鎖只有初始化時需要一次CAS。
“偏向”的意思是,偏向鎖假定將來只有第一個申請鎖的線程會使用鎖(不會有任何線程再來申請鎖),因此,只需要在Mark Word中CAS記錄owner(本質上也是更新,但初始值為空),如果記錄成功,則偏向鎖獲取成功,記錄鎖狀態為偏向鎖,以后當前線程等于owner就可以零成本的直接獲得鎖;否則,說明有其他線程競爭,膨脹為輕量級鎖。
偏向鎖無法使用自旋鎖優化,因為一旦有其他線程申請鎖,就破壞了偏向鎖的假定。
缺點:
同樣的,如果明顯存在其他線程申請鎖,那么偏向鎖將很快膨脹為輕量級鎖。
輕量級鎖:
自旋鎖的目標是降低線程切換的成本。如果鎖競爭激烈,我們不得不依賴于重量級鎖,讓競爭失敗的線程阻塞;如果完全沒有實際的鎖競爭,那么申請重量級鎖都是浪費的。輕量級鎖的目標是,減少無實際競爭情況下,使用重量級鎖產生的性能消耗,包括系統調用引起的內核態與用戶態切換、線程阻塞造成的線程切換等。
顧名思義,輕量級鎖是相對于重量級鎖而言的。使用輕量級鎖時,不需要申請互斥量,僅僅將Mark Word中的部分字節CAS更新指向線程棧中的Lock Record,如果更新成功,則輕量級鎖獲取成功,記錄鎖狀態為輕量級鎖;否則,說明已經有線程獲得了輕量級鎖,目前發生了鎖競爭(不適合繼續使用輕量級鎖),接下來膨脹為重量級鎖。
Mark Word是對象頭的一部分;每個線程都擁有自己的線程棧(虛擬機棧),記錄線程和函數調用的基本信息。二者屬于JVM的基礎內容,此處不做介紹。
當然,由于輕量級鎖天然瞄準不存在鎖競爭的場景,如果存在鎖競爭但不激烈,仍然可以用自旋鎖優化,自旋失敗后再膨脹為重量級鎖。
缺點:
同自旋鎖相似,如果鎖競爭激烈,那么輕量級將很快膨脹為重量級鎖,那么維持輕量級鎖的過程就成了浪費。
重量級鎖:
內置鎖(內置鎖是JVM提供的最便捷的線程同步工具,在代碼塊或方法聲明上添加synchronized關鍵字即可使用內置鎖)在Java中被抽象為監視器鎖(monitor)。在JDK 1.6之前,監視器鎖可以認為直接對應底層操作系統中的互斥量(mutex)。這種同步方式的成本非常高,包括系統調用引起的內核態與用戶態切換、線程阻塞造成的線程切換等。因此,后來稱這種鎖為“重量級鎖”。
偏向鎖、輕量級鎖、重量級鎖適用于不同的并發場景:
偏向鎖:無實際競爭,且將來只有第一個申請鎖的線程會使用鎖。
輕量級鎖:無實際競爭,多個線程交替使用鎖;允許短時間的鎖競爭。
重量級鎖:有實際競爭,且鎖競爭時間長。
另外,如果鎖競爭時間短,可以使用自旋鎖進一步優化輕量級鎖、重量級鎖的性能,減少線程切換。
如果鎖競爭程度逐漸提高(緩慢),那么從偏向鎖逐步膨脹到重量鎖,能夠提高系統的整體性能。
二、鎖優化、鎖消除、鎖粗化、自旋鎖、可重入鎖、阻塞鎖、死鎖:
1.鎖優化:減少鎖的持有時間(將同步方法改成同步代碼塊)、減少鎖的粒度(ConcurrentHashMap)、讀寫分離鎖代替獨占鎖(ReadWriterLock)、鎖分離(LinkedBlockingQueue)、鎖粗化。推薦https://www.cnblogs.com/xdecode/p/9137804.html
2.鎖消除:指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行削除。然后帶來一定的性能提升。
3.鎖粗化:通常情況下,為了保證多線程間的有效并發,會要求每個線程持有鎖的時間盡可能短,但是大某些情況下,一個程序對同一個鎖不間斷、高頻地請求、同步與釋放,會消耗掉一定的系統資源,因為鎖的講求、同步與釋放本身會帶來性能損耗,這樣高頻的鎖請求就反而不利于系統性能的優化了,雖然單次同步操作的時間可能很短。鎖粗化就是告訴我們任何事情都有個度,有些情況下我們反而希望把很多次鎖的請求合并成一個請求,以降低短時間內大量鎖請求、同步、釋放帶來的性能損耗。
4.自旋鎖:又稱非可重入鎖。簡單來說,若一個類中有兩個方法A、B,則AB都有獲得同一把鎖,||當A調用時獲得鎖,在A方法鎖還沒有被釋放時,調用B時,B無法獲得鎖。必須等A釋放鎖。首先,內核態與用戶態的切換上不容易優化。但通過自旋鎖,可以減少線程阻塞造成的線程切換(包括掛起線程和恢復線程)。因為鎖阻塞造成線程切換的時間與鎖持有的時間相當,所以減少線程阻塞造成的線程切換,就能得到較大的性能提升。
推薦:https://www.jianshu.com/p/36eedeb3f912
5.可重入鎖:簡單來說,若一個類中有兩個方法A、B,則AB都有獲得同一把鎖,||當A調用時獲得鎖,在A方法鎖還沒有被釋放時,調用B時,B也獲得鎖。專業說法:又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,再進入該線程的內層方法會自動獲取鎖(前提鎖對象得是同一個對象或者class),不會因為之前已經獲取過還沒釋放而阻塞。Java中ReentrantLock和synchronized都是可重入鎖,可重入鎖的一個優點是可一定程度避免死鎖。
6.阻塞鎖:阻塞鎖指改變了線程的運行狀態,在java中,線程Thread有如下幾種狀態:新建狀態、就緒狀態、運行狀態、阻塞狀態、死亡狀態。阻塞鎖,可以說是讓線程進入阻塞狀態進行等待,當獲得相應的信號(喚醒,時間) 時,才可以進入線程的準備就緒狀態,準備就緒狀態的所有線程,通過競爭,進入運行狀態。
7.死鎖:不同線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。
三、死鎖的原因:
四、死鎖的解決辦法:
- CountDownLatch、 CyclicBarrier 和Semaphore三個類的使用和原理:
JAVA并發包中有三個類用于同步一批線程的行為,分別是CountDownLatch、Semaphore和CyclicBarrier。
1.CountDownLatch是一個計數器閉鎖,通過它可以完成類似于阻塞當前線程的功能,即:一個線程或多個線程一直等待,直到其他線程執行的操作完成。CountDownLatch用一個給定的計數器來初始化,該計數器的操作是原子操作,即同時只能有一個線程去操作該計數器。
2.Semaphore與CountDownLatch相似,不同的地方在于Semaphore的值被獲取到后是可以釋放的,并不像CountDownLatch那樣一直減到底。它也被更多地用來限制流量,類似閥門的 功能。如果限定某些資源最多有N個線程可以訪問,那么超過N個主不允許再有線程來訪問,同時當現有線程結束后,就會釋放,然后允許新的線程進來。有點類似于鎖的lock與 unlock過程。相對來說他也有兩個主要的方法:
用于獲取權限的acquire(),其底層實現與CountDownLatch.countdown()類似;
用于釋放權限的release(),其底層實現與acquire()是一個互逆的過程。
推薦:https://www.jianshu.com/p/bb5105303d85
3.CyclicBarrier也是一個同步輔助類,它允許一組線程相互等待,直到到達某個公共屏障點(common barrier point)。通過它可以完成多個線程之間相互等待,只有當每個線程都準備就緒后,才能各自繼續往下執行后面的操作。類似于CountDownLatch,它也是通過計數器來實現的。那么CyclicBarrier和CountDownLatch之間的區別在于:
CountDownLatch主要是實現了1個或N個線程需要等待其他線程完成某項操作之后才能繼續往下執行操作,描述的是1個線程或N個線程等待其他線程的關系。CyclicBarrier主要是實現了多個線程之間相互等待,直到所有的線程都滿足了條件之后各自才能繼續執行后續的操作,描述的多個線程內部相互等待的關系。
CountDownLatch是一次性的,而CyclicBarrier則可以被重置而重復使用。
總結
- 上一篇: springMVC——SpringMVC
- 下一篇: 无招胜有招之多线程