多線程筆記(二)

来源:https://www.cnblogs.com/xuzhuo123/archive/2022/05/10/16253912.html
-Advertisement-
Play Games

多線程筆記(二) 1. Synchronized 和 Lock 的區別 synchronized是Java的關鍵字,是 JVM 層面的內置功能和實現。 Lock是一個介面,是代碼層面的實現 synchronized可以隱式的獲取,釋放鎖 lock是顯式的獲取,釋放鎖 synchronized在發生異 ...


多線程筆記(二)

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掉。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 當前的主瀏覽器都支持直接打開pdf文件,從而實現文件預覽。如果是其他格式文件則得下載,因此用openOffice實現文件轉pdf格式。 一、 openOffice的安裝 下載地址:http://www.openoffice.org/ 安裝教程可參考:openOffice下載和安裝 進入安裝目錄 ...
  • 本文只是通過一個實例來講述如何獲得python中所有的單字元的字母表,不僅僅是局限於英文的abcd,可能還有其他語言如ᵝᵞᵟᵠ等。在實際寫python的過程中可能不一定用得到,但是不失為一個挺有趣的功能探索。 ...
  • 在C/C++中有個叫指針的玩意存在感極其強烈,而說到指針又不得不提到記憶體管理。現在時不時能聽到一些朋友說指針很難,實際上說的是記憶體操作和管理方面的難。(這篇筆記咱也會結合自己的理解簡述一些相關的記憶體知識) 最近在寫C程式使用指針的時候遇到了幾個讓我印象深刻的地方,這裡記錄一下,以便今後回顧。 “經一 ...
  • 面向對象和麵向過程的區別 區別簡述 面向過程(Procedure Oriented):以過程為核心,強調**事件的流程、順序,**如:C語言。 面向對象(Object Oriented):以對象為核心,強調**事件的角色、主體,**如:C++、Java。 區別 1.思路不同 2.特點不同 3.優勢不 ...
  • 現象: 最近將pyinsatller升級到最新的 Version: 5.0.1版本後(之前一直用的是3.5版本同樣方法未遇到問題,今次更新到最新版本後5.0.1後打包就遇到問題,具體是這中間哪個版本開始有變化也不清楚了,也不去追究,凡是在新版本中遇到問題就在新版本中解決),詳細現象及解決辦法如下: ...
  • 昨天突發奇想想來玩一玩,然後安裝了一下午才成功,基本所有該踩的坑都踩了,但當時沒截圖,現在靠著記憶寫一下。 官網鏈接:https://www.mongodb.com/try/download/community。 需要註意的是:超過次數就必須要登錄才能下載。(證明我真的試過很多次) 1.最開始出現的 ...
  • 今天某個項目的數據有些問題,需要查詢日誌看看具體的情況 結果在執行 cat ***.log |grep "關鍵字" 命令後包如下錯誤: grep: memory exhausted 思路1: 既然提示 記憶體問題,是不是日誌文件太大了, 用 du -sh * 命令查看 後文件也就 300M 思路2: ...
  • 下載&安裝Cmake 進入下載頁面 Download | CMake 選擇安裝包版本 打開安裝包,下一步之後選擇添加path 選擇完安裝文件夾開始安裝 下載&配置OpenCV 進入下載頁面 Releases - OpenCV 選擇版本下載(我下的是 Sources,Windows版是已經構建好的,不 ...
一周排行
    -Advertisement-
    Play Games
  • 用例演示 - 創建實體 本節將演示一些示例用例並討論可選場景。 創建實體 從實體/聚合根類創建對象是實體生命周期的第一步。聚合/聚合根規則和最佳實踐部分 建議為Entity類創建一個主構造函數,以保證創建一個有效的實體。因此,無論何時我們需要創建實體的實例,我們都應該使用那個構造函數 參見下麵的問題 ...
  • 領域邏輯 & 應用邏輯 如前所述,領域驅動設計中的業務邏輯分為兩部分(層):領域邏輯和應用邏輯: 領域邏輯由系統的核心領域規則組成,應用邏輯實現應用特定的用例 雖然定義很明確,但實現起來可能並不容易。您可能無法決定哪些代碼應該位於應用程式層,哪些代碼應該位於領域層。本節試圖解釋其中的差異 多個應用程 ...
  • 表弟大學快畢業了,學了一個學期Python居然還不會寫學生管理系統,真的給我丟臉啊,教他又不肯學,還讓我直接給他寫,我真想兩巴掌上去,最終還是寫了給他,誰讓他是我表弟呢,關鍵時候還是得幫他一把! 寫完了放在那也是放著,所以今天分享給大家吧! 話不多說,咱們直接開始吧! 代碼解析 一、登錄頁面 1、定 ...
  • Zookeeper3.7源碼剖析 能力目標 掌握Zookeeper中Session的管理機制 能基於Client進行Debug測試Session創建/刷新操作 能搭建Zookeeper集群源碼配置 掌握集群環境下Leader選舉啟動過程 能說出Zookeeper選舉過程中的概念 能說出Zookeep ...
  • 前言 今天給大家分享一下我自己寫的筆記,純純的都是乾貨,關於字好像也能看。這是我學python整理出來的一些資料,希望對大家 有用。想要更多的資料那就的給一個關註了… python學習交流Q群:903971231### #導入Counter from collections import Count ...
  • Hi,大家好,我是Mic 一個工作5年的粉絲找到我。 他說: “Mic老師,你要是能回答出這個問題,我就佩服你” 我當場就懵了,現在打賭都這麼隨意了嗎? 我問他問題是什麼,他說“Kafka如何避免重覆消費的問題!” 下麵看看普通人和高手的回答! 普通人: Kafka怎麼避免重覆消費就是我們可以通過 ...
  • 前言 Steam是由美國電子游戲商Valve於2003年9月12日推出的數字發行平臺,被認為是電腦游戲界最大的數位發行平臺之一,Steam平臺是全球最大的綜合性數字發行平臺之一。玩家可以在該平臺購買、下載、討論、上傳和分享游戲和軟體。 而每周的steam會開啟了一輪特惠,可以讓游戲打折,而玩家就會 ...
  • 本篇內容將在上一篇已有的內容基礎上,進一步的聊一下項目中使用JPA的一些高階複雜場景的實踐指導,覆蓋了主要核心的JPA使用場景,可以讓你在需求開發的時候對JPA的使用更加的游刃有餘。 ...
  • 1.路徑處理 1.找模塊:sys.path import sys print(sys.path) - 1.理解 - 1.是python去查找包或模塊 - 2.項目開始根目錄,python內置的目錄 - 3.雖然說python的安裝目錄下也可以存放我們寫的模塊,但是不建議(太多了,不大好找) - 4. ...
  • Go 語言入門練手項目系列 01 基於命令行的圖書的增刪查改 02 文件管理 持續更新中... > 本文來自博客園,作者:Arway,轉載請註明原文鏈接:https://www.cnblogs.com/cenjw/p/gobeginner-proj-bookstore-cli.html 介紹 這是一 ...