JUC---各種鎖(JDK13)

java.util.concurrent包系列文章
JUC—ThreadLocal源碼解析(JDK13)
JUC—ThreadPoolExecutor線程池源碼解析(JDK13)
JUC—各種鎖(JDK13)
JUC—原子類Atomic*.java源碼解析(JDK13)
JUC—CAS源碼解析(JDK13)
JUC—ConcurrentHashMap源碼解析(JDK13)
JUC—CopyOnWriteArrayList源碼解析(JDK13)
JUC—併發隊列源碼解析(JDK13)
JUC—多線程下控制併發流程(JDK13)
JUC—AbstractQueuedSynchronizer解析(JDK13)


本篇偏概念性,附帶部分源碼。

常用的鎖

常用的2種加鎖方式synchronized和Lock。

synchronized的缺點

  • 效率低,試圖獲得鎖時不能設定超時,不能中斷一個正在試圖獲得鎖的線程
  • 不夠靈活,加鎖和釋放的時機單一,每個鎖僅有單一的條件(某個對象)
  • 無法知道是否成功獲取到鎖

Lock作爲synchronized的一種補充,他們都是可重入鎖。
Lock的主要方法

  • lock():獲取鎖,如果鎖被其他線程獲取,則等待。lock()不會像synchronized一樣在異常時自動釋放鎖,必須在finally中釋放鎖。lock()方法不能被中斷,一旦陷入死鎖,lock()就會陷入永久等待
  • tryLock():嘗試獲取鎖,如果當前鎖沒有被其他線程佔用,則獲取成功,則返回true,否則返回false,代表鎖獲取失敗。不等待,立刻返回
  • tryLock(long time,TimeUnit unit):超時就放棄
  • lockInterruptibly():相當於tryLock(long time,TimeUnitunit)把超時時間設置爲無限。在等待鎖的過程中可以被中斷。 unlock():必須在finally中釋放鎖

鎖的分類

在這裏插入圖片描述

互斥同步鎖(悲觀鎖),

會鎖住被操作的對象
缺點

  • 阻塞和喚醒帶來的性能劣勢
  • 可能會陷入永久阻塞,死鎖

例子

  • Java中悲觀鎖的實現就是synchronized和Lock相關類
  • 數據庫中selec for update也是悲觀鎖

適合於併發寫很多的情況,適用於臨界區持鎖有時間比較長的情況,可以避免大量的無用自旋操作

非互斥同步鎖(樂觀鎖)

不會鎖住被操作的對象,在更新時會去檢查我修改期間數據有沒有被其他線程修改過,如果沒有被修改過,就正常修改數據,如果發現數據被修改過,就會選擇放棄,重試,報錯等策略。樂觀鎖的實現一般都是使用CAS實現的。
缺點

  • 可能會導致大量的無用的自旋操作,消耗CPU資源

例子

  • Java中樂觀鎖的實現就是原子類和併發容器等
  • 數據庫中version來控制更新就是樂觀鎖

適合於併發寫入少,讀很多的情況,不加鎖能讓讀取性能大幅提高

可重入鎖

同一個線程可以多次獲取同一把鎖,synchronized和ReentrantLock都是可重入鎖。state次數+1。

公平鎖與非公平鎖

公平是指按照線程請求的順序來分配鎖,非公平是指不完全按照請求的順序分配鎖,在一定情況下,可以插隊。設計非公平鎖是爲了提高效率,避免喚醒線程帶來的空檔期。把已經掛起的線程喚醒的那段時間是有開銷的。如果是公平的,這段時間誰都沒辦法拿到鎖。ReentrantLock默認非公平鎖。

  • tryLock():本身是自帶插隊熟悉的。即時已經等待隊列中已經有其他線程了。

在這裏插入圖片描述

公平鎖在獲取鎖之前會判斷有沒有線程在隊列中
在這裏插入圖片描述

非公平鎖不管有沒有線程在隊列中都直接嘗試去獲取鎖

在這裏插入圖片描述

共享鎖和排他鎖

  • 排他鎖:又叫獨佔鎖,獨享鎖。獲取鎖之後可以做修改查詢。
  • 共享鎖:又叫讀鎖。可以查看但是不能修改數據。

ReentrantReadWriteLock
讀寫鎖的規則

  • 多個線程可以同時獲取讀鎖
  • 如果一個先獲得了讀鎖,其他線程無法申請寫鎖,會等待。
  • 如果一個線程獲取了寫鎖,其他線程都不能獲取寫,讀鎖。
  • 要麼一個多個讀。要麼一個寫。多讀一寫。

讀寫鎖插隊策略
公平鎖:不允許插隊
非公平鎖:

  • 寫鎖隨時插隊讀
  • 鎖僅在等待隊列頭節點不是想獲取寫鎖的線程的時候可以插隊

公平鎖的情況下讀寫都需要判斷隊列是否有等待線程

在這裏插入圖片描述

非公平鎖
寫線程不關心等待隊列是否有線程在等待,可以插隊。
讀線程需要判斷等待隊列頭結點是否是排他鎖(讀鎖)。

在這裏插入圖片描述

鎖的升降級
支持鎖的降級不支持升級,線程拿到寫鎖正在執行,中途需要執行某一個讀鎖鎖定的方法,是可以的。不用先釋放寫鎖,再去嘗試獲取這個讀鎖。提高了整體效率。在持有寫鎖的同時獲取讀鎖。
如果支持升級的話容易造成死鎖。兩個線程都持有讀鎖,同時又都升級寫鎖,都要等待對方釋放讀鎖,就會造成死鎖。

自旋鎖和阻塞鎖

  • 自旋鎖:阻塞和喚醒一個線程需要操作系統切換CPU狀態來完成,這種狀態轉換需要耗費處理器時間。讓線程自旋,如果在自旋完成後前面鎖定同步資源的線程 已經釋放了鎖,那麼當前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。但是如果長時間自旋,也會耗費CPU資源。原子類Atomic*,就是利用無限循環加上CAS實現自旋。
  • 阻塞鎖:如果沒拿到鎖,會直接把線程阻塞,直到被喚醒

  • 我的公衆號:Coding摳腚
  • 偶爾發發自己最近學到的乾貨。學習路線,經驗,技術分享。技術問題交流探討。
    Coding摳腚
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章