多線程筆記(一)

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

多線程筆記(一) 1. sleep()方法和yield()方法 共同點:讓當前線程釋放cpu資源,讓其他線程來運行 不同點:調用sleep()方法後,線程進入到TIMED_WAITING狀態,等待超時後進入RUNNABLE狀態,開始搶占CPU資源。調用yield()方法後,線程進入RUNNABLE狀 ...


多線程筆記(一)

1. sleep()方法和yield()方法

  • 共同點:讓當前線程釋放cpu資源,讓其他線程來運行

  • 不同點:調用sleep()方法後,線程進入到TIMED_WAITING狀態,等待超時後進入RUNNABLE狀態,開始搶占CPU資源。調用yield()方法後,線程進入RUNNABLE狀態,直接開始搶占CPU資源。

2. 偏向鎖和可重入鎖的區別

  • 偏向鎖是指一個線程訪問同步塊的時候,第一次會獲取鎖,在沒有其他線程競爭鎖的情況下再訪問同步塊不需要再獲取鎖,直接訪問同步塊。節省了獲取鎖和釋放鎖的開銷。

  • 可重入鎖是指一個線程訪問同步塊1需要鎖A並獲得鎖A,接下來訪問另一個同步塊2也需要鎖A,線上程持有鎖A的情況下訪問同步塊2時不需要再獲取鎖。

3. wait, notify/notifyAll註意事項

  • wait, notify/notifyAll 必須放到同步塊或者同步方法裡面去執行
  • 註意使用鎖的對象來調用wait, notify/notifyAll
  • 其底層使用的是Monitor機制,wait過後線程會進入monitor對象的對應的WaitSet
  • 調用wait方法後就會釋放鎖
  • 調用notify/notifyAll方法後並不會立即獲得鎖。要等前面的線程釋放鎖之後再去爭搶鎖

4. Java記憶體模型(JMM)

  • 所有變數(共用的)都存儲再主記憶體中,每個線程都有自己的工作記憶體,工作記憶體中保存該線程使用到的變數的主記憶體副本拷貝
  • 線程對變數的所有操作(讀,寫)都應該再工作記憶體中完成
  • 不同線程不能相互訪問工作記憶體,交互數據要通過主記憶體

記憶體之間的交互操作

  • lock: 鎖定,把變數表示為線程獨占,作用於主記憶體變數

  • read: 讀取,把變數值從主記憶體讀取到工作記憶體

  • load: 載入,把read讀取到的值放入工作記憶體的變數副本中

  • use: 使用,把工作記憶體中的一個變數的值傳遞給執行引擎

  • assign: 賦值,把從執行引擎接收到的值賦給工作記憶體裡面的變數

  • store: 存儲,把工作記憶體中的一個變數的值傳遞到主記憶體中

  • write: 寫入,把store進來的數據存放如主記憶體的變數中

  • unlock: 解鎖,把鎖定的變數釋放,別的線程才能使用,作用於主記憶體變數

記憶體間交互操作的規則

  • 不運行read和load;store和write操作之一單獨出現,以上兩個操作必須按順序執行,但不保證連續執行,也就是說,read和load;store和write之間是可以插入其他指令的
  • 不允許一個線程丟棄它的最近的assign操作,即變數在工作記憶體中改變了之後必須把該變化同步回主記憶體
  • 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從線程的工作記憶體同步回主記憶體中
  • 一個新的共用變數只能從主記憶體中“誕生”,不允許在工作記憶體中直接使用一個未被初始化的變數,也就是對一個變數實施use和store操作之前,必須先執行過了load和assign操作
  • 一個共用變數在同一時刻只允許一個線程對其執行lock操作,但lock操作可以被同一個線程重覆執行多次,多次執行lock後,只有執行相同次數的unlock操作,變數才會被解鎖
  • 如果對一個共用變數執行lock操作,將會清空工作記憶體中此變數的值,在執行引擎使用這個變數前,需要重新執行load
  • 如果一個共用變數沒有被lock操作鎖定,則不允許對它執行unlock操作,也不能unlock一個被其他線程鎖定的共用變數
  • 對一個共用變數執行unlock操作之前,必須先把此變數同步回主記憶體(執行store和write操作)

5. 併發編程三大特性

原子性:一個操作要麼全部執行成功,要麼全部執行失敗

可見性:一個線程修改了共用變數之後,其他線程能夠立刻看到這個修改

有序性:程式執行的順序是按照代碼的邏輯先後循序來執行的

6. 重排序

編譯器或處理器為了優化程式的執行性能,對指令執行的順序重新排列

目的:儘可能減少寄存器的讀取和存儲次數,復用寄存器存儲的數據

分類

  • 編譯器重排序:編譯器再不改變程式在單線程環境下運行的語義的前提下,重新安排語句的執行順序
  • 指令重排序:處理器將多條指令並行執行,如果不存在數據依賴,處理器可以改變語句對應的指令的執行順序
  • 記憶體系統重排序:處理器使用緩存和讀寫緩衝區,使得數據的載入存儲操作看上去是亂序執行的

數據依賴

如果兩個操作訪問同一個共用變數,而且兩個操作裡面有一個為寫操作,那麼這兩個操作直接就存在數據依賴性。

具有數據依賴性的指令是不會被重排的

數據依賴的分類:

  • 讀後寫:讀一個變數過後,再寫這個變數
  • 寫後寫:寫一個變數過後,再寫這個變數
  • 寫後讀:寫一個變數過後,再讀這個變數

as-if-serial語義

不管有沒有重排序,也不關心如何進行重排序,單線程環境下,程式的執行結果不會被改變。

7. happens-before

  1. 如果一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見(保障可見性)

    且第一個操作的執行順序排在第二個操作之前(JMM對程式員做出的一個邏輯保障,並不是代碼指令真正的執行保障)

  2. 即使兩個操作之間存在happens-before關係,並不意味著Java平臺的實現必須要按照happens-before關係指定的順序來執行

第一條是JMM對程式員做出的邏輯保障

第二條是JMM對編譯器,處理器進行重排序的約束原則:只要不改變程式的執行結果(不管是單線程還是多線程),愛怎麼排怎麼排

happens-before規則

  • 程式順序規則:一個線程中的每個操作 happens-before 該線程中的任意後續操作

  • 監視器鎖規則:對一個鎖的解鎖操作 happens-before 隨後對這個鎖的加鎖(就是先釋放鎖,後加鎖)

  • volatile變數規則:對一個volatile修飾的欄位進行的寫操作 happens-before 任意後續對這個欄位進行的讀操作

  • 傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C

  • start規則:如果線程A里執行操作ThreadB.start()(啟動線程B),那麼A線程的ThreadB.start()操作 happens-before 於線程B中的任意操作

  • join規則:如果線程A執行操作ThreadB.join()併成功返回,那麼線程B中的任意操作happens-before於線程A從ThreadB.join()操作成功返回。

8. 記憶體屏障

記憶體屏障是一種屏障指令,它使得處理器或編譯器對屏障指令的前面和後面所發出的記憶體操作,執行一個排序的約束。也叫記憶體柵欄或柵欄指令

作用

  • 阻止屏障兩邊的指令重排序
  • 寫數據的時候加了屏障的話,強制把寫緩衝區的數據刷回到主記憶體中
  • 讀數據的時候加了屏障的話,讓工作記憶體或CPU高速緩存當中緩存的數據失效,重新到主記憶體中獲取新的數據

分類

讀屏障:Load Barrier: 在讀指令之前插入讀屏障,讓工作記憶體或CPU高速緩存當中緩存的數據失效,重新到主記憶體中獲取新的數據

寫屏障:Store Barrier: 在寫指令之後插入寫屏障,強制把緩衝區的數據刷回到主記憶體中

9. 重排序與記憶體屏障

JVM本身為了保證可見性:

對於編譯器的重排序,JMM會根據重排序的規則,禁止特定類型的編譯器重排序

對於處理器的重排序,Java編譯器在生成指令序列的適當位置,插入記憶體屏障指令,來禁止特定類型的處理器排序

JMM的記憶體屏障

  • LoadLoad Barriers:

示例:Load1; LoadLoad; Load2

禁止重排序,訪問Load2的讀取操作一定不會重排到Load1之前。由於在讀指令之前插入讀屏障,所有會保證Load2在讀取的時候,自己緩存內相應數據失效,Load2會重新到主記憶體中獲取最新的數據

  • LoadStore Barriers:

示例:Load1; LoadStore; Store2

禁止重排序,一定是Load1讀取數據完成後,才能讓Store2寫操作的數據寫入到主記憶體

  • StoreStore Barries:

示例:Store1; StoreStore; Store2

禁止重排序,一定是Store1的數據寫入主記憶體後,才能讓Store2寫操作的數據寫入主記憶體。由於在寫指令之後插入寫屏障,所以會保證Store1寫出的數據強制刷回到主記憶體中

  • StoreLoad Barries:

示例:Store1; StoreLoad; Load2

禁止重排序,一定是Store1的數據寫入主記憶體後,才能讓Load2讀取數據。由於在寫指令之後插入寫屏障,所以會保證Store1寫出的數據強制刷回到主記憶體中。由於在讀指令之前插入讀屏障,所有會保證Load2在讀取的時候,自己緩存內相應數據失效,Load2會重新到主記憶體中獲取最新的數據。

為什麼說StoreLoad Barries是最重(和記憶體交互次數多,交互延遲較大)的?

因為其既要保證讀屏障也要保證寫屏障

擴展

這些屏障指令並不是處理器真實的執行指令,它們知識JMM定義出來的,跨平臺的指令。因為不同硬體實現記憶體屏障的方式並不相同,JMM為了屏蔽這種底層硬體平臺的不同,抽象出了這些記憶體屏障指令,在運行的時候,由JVM來為不同的平臺生成相應的機器碼。這些記憶體屏障指令,在不同的硬體平臺上,可能會做一些優化,從而只支持部分的JMM的記憶體屏障指令

10. Volatile關鍵字

volatile修飾的變數有如下特點:

  • 保證可見性
  • 不保證原子性
  • 禁止指令重排
  1. 對一個volatile修飾的變數進行讀操作的話,總是能夠讀到這個變數的最新的值,也就是這個變數最後被修改的值

  2. 一個線程修改了volatile修飾的變數的值的時候,那麼這個變數的新的值,會立即刷新回到主記憶體中

  3. 一個線程去讀取volatile修飾的變數的時候,該變數在工作記憶體中的數據無效,需要重新到主記憶體去讀取最新的數據

volatile記憶體語義

volatile寫的記憶體語義:寫一個volatile變數時,JMM會把該線程對應的工作記憶體中的共用變數的值刷新到主記憶體中

volatile讀的記憶體語義:讀一個volatile變數時,JMM會把線程對應的工作記憶體中的共用變數數據設置為無效的,然後從主記憶體中去讀共用變數最新的數據

volatile記憶體語義的實現

  1. 位元組碼層面:

    它影響的是Class內的Field的 falgs ,添加了一個ACC_VOLATILE。JVM在把位元組碼生成為機器碼的時候,發現操作的是volatile變數的話,就回根據JMM的要求,在相應的位置去插入記憶體屏障

  2. JMM層面:

第一個操作 第二個操作(普通讀寫) 第二個操作(volatile讀) 第二個操作(volatile寫)
普通讀寫 不允許重排序
volatile讀 不允許重排序 不允許重排序 不允許重排序
volatile寫 不允許重排序 不允許重排序

​ volatile寫之前的操作都禁止重排序到volatile之後

​ volatile讀之後的操作都禁止重排序到volatile之前

​ volatile寫之後的volatile讀,禁止重排序

為了實現volatile記憶體語義,按如下方式插入記憶體屏障

​ (1)在每個volatile寫操作的前面插入一個StoreStore屏障

​ (2)在每個volatile寫操作的後面插入一個StoreLoad屏障

​ (3)在每個volatile讀操作的後面插入一個LoadLoad屏障

​ (4)在每個volatile讀操作的後面插入一個LoadStore屏障

  1. 處理器層面

​ CPU執行機器碼指令的時候,是使用 lock 首碼指令來實現volatile的功能的

​ lock指令相當於記憶體屏障,功能也類似於記憶體屏障的功能

​ (1)首先對匯流排/緩存加鎖,然後去執行後面的指令,最後釋放鎖,同時把CPU高速緩存的數據刷新回到主記憶體

​ (2)在lock鎖住匯流排/緩存的時候,其他CPU的讀寫請求就會被阻塞,直到鎖被釋放。lock過後的寫操作,讓會其他CPU的高速緩存中相應的數據失效,這樣後續這些CPU在讀取數據的時候,就會從主記憶體去載入最新的數據


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

-Advertisement-
Play Games
更多相關文章
  • **導讀:**聯友科技是一家旨在提供在汽車行業全價值鏈解決方案的科技公司。公司以數字化、智能零部件以及智能網聯為三大核心業務領域,涵蓋研發/製造/營銷等領域的信息化產品、系統運行維護服務、雲服務、大數據分析服務、智能網聯及數字化運營服務、車載智能部件及汽車設計等業務。本次分享會圍繞以下四點展開: 車 ...
  • 1. 啟動並下載一個clickhouse-server, By default, starting above server instance will be run as default user without password. docker run -d --name ch-server - ...
  • 前言 有段時間沒寫技術文章了,一是因為工作太忙,再者因為本人文筆實在一般。最近終於閑下來,本著分享的目的將一些組件設計上的心得與大家分享。 本篇文章是基於原有一篇關於支付文章的進一步優化設計,所以在閱讀本篇文章前還是建議先移步到那篇文章。 文章地址: 微信、支付寶、銀聯、Paypal 支付組件封裝 ...
  • js中關於原型和原型鏈有 __proto__ 、prototype、constructor 頻頻出現在面試題中,但是記得多了反而容易記混。 這裡簡單總結下每個屬性的使用場景,方便記憶。 對象和函數都有 __proto__,對象的 __proto__指向構造函數的prototype,構造函數的__pr ...
  • 今天是vue基礎、vue核心內容第三天,也是最後一天,後面開始進入組件化學習,整個基礎內容以生命周期的結束而結束,不得不說,張天禹把這節課講活了,開始覺得vue是一個有生命的東西,包括前面所說的很多臟活累活都給他做,我們只管調用,說的我都於心不忍如此對待vue了,所以思來想去,我絕對對待它最好的辦法 ...
  • https://www.cnblogs.com/yeungchie/ PyQt5 from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * PySide2 from PySide2.QtWid ...
  • 前言 輸入系統,輸入某個鍵,響應到GamePlay層做對應的事。例如 點擊滑鼠,前進還是開槍之類,是如何響應的。這裡只說應用層邏輯,硬體層邏輯不講述。 詳解 1.問題來源 先看下麵一個例子:跳躍的事件響應堆棧 從上述堆棧我們不難發現,疑惑點主要集中於 APlayerControllerProcess ...
  • 本文參考社長的 TinyWebServer 庖丁解牛 epoll 常用API epoll_create 函數 #include <sys/epoll.h> int epoll_create(int size); 創建一個指示 epoll 內核事件表的文件描述符,該描述符將用作其他 epoll 系統調 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...