多執行緒筆記(二)
1. Synchronized 和 Lock 的區別
-
synchronized是Java的關鍵字,是 JVM 層面的內置功能和實作,
Lock是一個介面,是代碼層面的實作
-
synchronized可以隱式的獲取,釋放鎖
lock是顯式的獲取,釋放鎖
-
synchronized在發生例外的時候會自動釋放鎖
lock在發生例外的時候,不會自動釋放鎖,必須要呼叫unlock方法才會釋放鎖,否則容易引起死鎖
-
Lock可以嘗試非阻塞獲取鎖,可中斷獲取鎖,超時獲取鎖,
synchronized并沒有這些功能
2. LockSupport
LockSupport是一個編程工具類,主要是為了阻塞執行緒(park)和喚醒執行緒(unpark)時使用
設計原理的核心:許可
? park:掛起當前執行緒,等待一個許可
? unpark:為某個執行緒提供一個許可,喚醒某個指定的執行緒
park/unpark和wait/notify很類似,但其具有以下的優點
- park/unpark是以thread為操作物件,語意更加直觀
- 操作更為精準和靈活,可以準確的去喚醒某一個執行緒
park/unpark和wait/notify的區別
wait/notify和synchronized聯系在一起的,wait過后,執行緒是進入Blocked狀態
park方法使當前執行緒掛起,進入到waiting狀態
3. CAS
CAS(Compare And Swap, 比較并替換)中有3個基本的運算元:V:記憶體地址的值; A:舊的預期的值;B:要修改的新的值
基本實作方式:
使用CAS去更新一個變數的時候,只有變數的舊的預期的值A 和記憶體地址的值V 相同的時候,才會將V 修改為新的值B,如果修改失敗,會自旋等待,直到修改成功,
CAS實作的基石:Unsafe類
CAS想要保證操作時執行緒安全的,一個實作的關鍵在于如何保證 比較并替換 是一個原子操作
在Java中,用Unsafe類來實作CAS的原子操作,Unsafe類 ==> JNI(Java本地介面) ==>本地實作的C++庫 ==>操作記憶體空間
CAS在Java中的應用和缺點
應用:
-
Atomic包, Lock包下系列的類
-
在JDK1.6以后,sychronized升級為重量級鎖之前也采用的CAS機制
缺點
-
CAS采用自旋的方式,會浪費CPU的資源
-
不能保證代碼塊的原子性,保證的是對一個變數的 比較和替換 的操作是原子的
-
ABA問題:CAS操作記憶體值,由A改成了B,但是又改回了A,從而導致后續本不應該成功的操作,最后成功執行
? 自己舉個栗子:由于網路延時,執行緒一執行緒二都想對記憶體值A操作,目的就是將記憶體值A改成B(只修改一次),按理說一個執行緒操作成功,那另一個執行緒就要操作失敗,執行緒一和執行緒二取到的舊的值都是A,假定執行緒一操作成功,將A改成了B,按理說接下來執行緒二拿到的記憶體的值是B,和取到的舊的值A比較,B不等于A,就會提交失敗,但是捏,好巧不巧,在執行緒二修改之前,執行緒三過來執行它自己的任務,將B改成了A,這個時候執行緒二拿到的記憶體值是A,之前取到的舊的值也是A,A等于A,執行緒二就會對A進行修改,這個時候就對記憶體值修改了兩次,而我們只想讓它修改一次,就出錯了,可以把記憶體值想成自己的工資,誰都不想自己的工資被莫名其妙的多改幾次把,改多了當我沒說,哈哈哈,
ABA的解決方案:給資料加上版本號,每次不僅要比較記憶體的值,還要比較版本號
4. AQS
AQS是什么?
AQS(AbstractQueuedSynchronizer,抽象佇列同步器)是構建鎖和其他同步組件的基礎框架
AQS能干什么?
- 同步佇列的管理和維護
- 同步狀態的光臨
- 執行緒的阻塞,喚醒的管理
基本設計思路
- 把競爭的執行緒和等待狀態,封裝成為Node物件
- AQS把這些Node,放到一個同步佇列中去,這個同步佇列是一個FIFO(先進先出)的一個雙向佇列,是基于CLH(貢獻者名字縮寫首字母,不用糾結這個)佇列實作的

- AQS使用int型別的成員變數來表示同步狀態,比如:是否有執行緒獲取鎖,鎖的重入次數等,具體的含義由具體的子類來定義
- AQS使用LockSupport來實作對執行緒的喚醒和阻塞,執行緒的喚醒和阻塞便隨著同步佇列的維護,
AQS如何把基礎功能提供出去?
AQS使用模板方法模式,大概的意思是規定了整體的流程,自己可以具體實作子流程,整體的流程是不能變的,后續把設計模式學了再做補充
非阻塞的獲取獨占鎖的流程
自己畫的簡化版流程,沒有涉及到里面的中斷

AQS中獲取和釋放獨占鎖和共享鎖區別
獨占鎖:正常情況下,只有持有鎖的執行緒運行結束了,釋放鎖了,該節點才會出隊,
共享鎖:當前節點喚醒了下一個節點并且將下一個節點設定尾Head之后,該節點出隊,
獨占鎖:只有在釋放鎖的時候,才會去看看要不要喚醒下一個節點,
共享鎖:在獲取鎖的程序中會在兩個地方看看要不要去喚醒下一個節點,一個是在獲取鎖的流程中呼叫setHeadAndPropagate()方法的時候,一個是在釋放鎖的時候,
5. ReentrantLock
ReentrantLock是Lock介面的實作,主要實作了可重入的獨占鎖的功能,與synchronized關鍵字功能型別
ReentrantLock與synchronized對比
ReentrantLock功能更加強大和靈活
- 可非中斷的獲取鎖
- 可中斷式的獲取鎖
- 可超時獲取鎖
- 提供了公平鎖和非公平鎖
公平鎖和非公平鎖的卻別主要體現在獲取鎖的方式上
公平鎖:多個執行緒按照申請獲取鎖的先后順序來獲取鎖
非公平鎖:多個執行緒按照不是按照申請獲取鎖的先后順序來獲取鎖,比如搶占式獲取鎖,高并發的情況可能會造成饑餓現象
在ReentrantLock的原始碼中,公平鎖主要是通過判斷當前的AQS佇列是否有節點來控制當前節點是否獲得鎖,佇列中如果有節點那么tryAcquire()方法直接回傳false表示獲取鎖失敗,再將節點其排到佇列末尾,
ReentrantReadWriteLock
在實際的業務中,往往讀資料比寫資料更加頻繁,如果我們對讀資料使用共享鎖,對寫資料使用獨占鎖,那么整個讀寫的性能就會提高,
讀鎖:用在讀取臨界資源的地方
寫鎖:用在更新臨界資源的地方
讀鎖和寫鎖的互斥規則:
- 一個執行緒讀,另一個執行緒讀:共享
- 一個執行緒讀,另一個執行緒寫:互斥
- 一個執行緒寫,另一個執行緒讀:互斥
- 一個執行緒寫,另一個執行緒寫:互斥
ReadWriteLock是一個介面,該介面中只有兩個方法,分別為Lock readLock();和Lock writeLock();
ReentrantReadWriteLock:可重入式讀寫鎖,是讀寫鎖(ReadWriteLock)的實作類,
- 支持讀鎖和寫鎖
- 支持公平鎖和非公平鎖
- 支持可重入鎖
- 支持鎖降級(如果一個執行緒持有寫鎖,在不釋放寫鎖的情況下,它還可以繼續持有讀鎖,這種情況就是鎖降級)
讀寫鎖的狀態存盤機制
AQS里的state是一個int值,在讀寫鎖中,需要同時保存兩種鎖的狀態,其同樣使用int型別的變數表示state,總共32位,前16位表示讀鎖的同步狀態,后面16位表示寫鎖的同步狀態,獲取讀鎖狀態就將state無符號右移16位,獲取寫鎖狀態就將state與掩碼相與,保留后16位,
6. StampedLock類
ReentrantReadWriteLock中存在著一些問題,寫執行緒可能會出現“饑餓”問題;如果有執行緒在讀,那么寫執行緒是無法獲取寫鎖的,
優點:
在Java8中引入了StampedLock,其對ReentrantReadWriteLock進行了增強,優化了讀鎖和寫鎖的訪問,使讀寫鎖之間可以相互轉換,因此可以更細粒度地控制并發,
缺點:
其設計初衷使作為一個內部工具類來使用,用于輔助開發其他的執行緒安全組件,用不好的花會產生死鎖,產生莫名其妙的問題,不支持可重入也是一個問題,
特點:
- 所有獲取鎖的方法,都會回傳一個stamp
- 所有釋放鎖的方法,都需要一個stamp
- 是不可重入的
- 有三種訪問方式,分別為讀模式,寫模式,樂觀讀模式
- 支持讀鎖和寫鎖的相互轉換
- 不支持Condition
7. Condition
該介面對原生的wait, notify/notifyAll這些方法進行增強,從Java語言層面,實作類似的功能,
AQS是使用同步佇列來控制節點獲取鎖,在Condition中使用條件佇列來控制節點什么時候await(),什么時候signal(),與多個節點共用一個同步佇列不同的是,一個Conditon物件就對應一個條件佇列,
總體流程為,呼叫await()時,將節點加入等待佇列,然后將執行緒掛起,等待其他執行緒對其呼叫signal()方法,其他執行緒對其呼叫signal()方法后,將該節點從條件佇列中出隊,將其添加到同步佇列的末尾,然后將其喚醒,然后就走同步佇列的那一套流程,
8. ThreadLocal
ThreadLocal是用來存放執行緒自身相關資料的一個容器,提供執行緒本地變數,訪問這個變數的每個執行緒都會有這個變數的一個副本,執行緒操作資料的時候就會操作執行緒本地的資料,從而避免了執行緒安全性問題,
threadLocals其實是一個ThreadLocalMap型別的,在Thread類中的一個屬性,伴隨的執行緒的存在而存在,當我們設定ThreadLocal變數的時候,ThreadLocalMap中的key就是ThreadLocal,value就是ThreadLocal變數的值,
-
由于threadLocals是Thread的一個屬性,會跟著執行緒一直存在,為了避免記憶體溢位,在確定ThreadLocal資料以后不再使用后,要及時remove掉,
-
由于ThreadLocalMap使用ThreadLocal的弱參考作為key,如果一個ThreadLocal沒有外部關聯的強參考,在垃圾回收的時候,JVM會回收掉ThreadLocal,就會出現ThreadLocalMap中key為空,但是value值還在,造成記憶體泄漏,所以在確定ThreadLocal資料以后不再使用后,要及時remove掉,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/472238.html
標籤:其他
上一篇:如何寫出一手好 SQL ?
