多執行緒筆記(一)

2022-10-10 18:00:17 字數 3162 閱讀 3205

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

可見性:乙個執行緒修改了共享變數之後,其他執行緒能夠立刻看到這個修改

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

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

目的:盡可能減少暫存器的讀取和儲存次數,復用暫存器儲存的資料

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

具有資料依賴性的指令是不會被重排的

資料依賴的分類:不管有沒有重排序,也不關心如何進行重排序,單執行緒環境下,程式的執行結果不會被改變。

且第乙個操作的執行順序排在第二個操作之前(jmm對程式設計師做出的乙個邏輯保障,並不是**指令真正的執行保障)

第一條是jmm對程式設計師做出的邏輯保障

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

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

讀屏障:load barrier: 在讀指令之前插入讀屏障,讓工作記憶體或cpu快取記憶體當中快取的資料失效,重新到主記憶體中獲取新的資料

寫屏障:store barrier: 在寫指令之後插入寫屏障,強制把緩衝區的資料刷回到主記憶體中

jvm本身為了保證可見性:

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

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

示例:load1; loadload; load2

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

示例:load1; loadstore; store2

禁止重排序,一定是load1讀取資料完成後,才能讓store2寫操作的資料寫入到主記憶體

示例:store1; storestore; store2

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

示例:store1; storeload; load2

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

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

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

擴充套件

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

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

對乙個volatile修飾的變數進行讀操作的話,總是能夠讀到這個變數的最新的值,也就是這個變數最後被修改的值

乙個執行緒修改了volatile修飾的變數的值的時候,那麼這個變數的新的值,會立即重新整理回到主記憶體中

乙個執行緒去讀取volatile修飾的變數的時候,該變數在工作記憶體中的資料無效,需要重新到主記憶體去讀取最新的資料

volatile寫的記憶體語義:寫乙個volatile變數時,jmm會把該執行緒對應的工作記憶體中的共享變數的值重新整理到主記憶體中

volatile讀的記憶體語義:讀乙個volatile變數時,jmm會把執行緒對應的工作記憶體中的共享變數資料設定為無效的,然後從主記憶體中去讀共享變數最新的資料

位元組碼層面:

它影響的是class內的field的falgs,新增了乙個acc_volatile。jvm在把位元組碼生成為機器碼的時候,發現操作的是volatile變數的話,就回根據jmm的要求,在相應的位置去插入記憶體屏障

jmm層面:

第乙個操作

第二個操作(普通讀寫)

第二個操作(volatile讀)

第二個操作(volatile寫)

普通讀寫

不允許重排序

volatile讀

不允許重排序

不允許重排序

不允許重排序

volatile寫

不允許重排序

不允許重排序

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

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

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

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

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

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

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

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

處理器層面

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

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

​ (1)首先對匯流排/快取加鎖,然後去執行後面的指令,最後釋放鎖,同時把cpu快取記憶體的資料重新整理回到主記憶體

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

多執行緒學習筆記 一

繼承thread類來封裝要同步內容 class testthread extends thread 實現runnable來實現並封裝要同步內容 class testrunnable implements runnable public class startrunnable 中斷執行緒new tes...

python 多執行緒筆記一

python thread,threading模組提供了對多執行緒的支援,不過通常我們使用threading模組來進行多執行緒程式設計。多執行緒模組使用方法 1.使用thread模組中的start new thread function,元組引數 字典引數 來執行函式,通常直接在python she...

java 多執行緒筆記(一)

首先,為了啟動乙個新的執行緒在虛擬機器中執行,要構造thread類的乙個例項,並呼叫start 方法。thread t new thread 當然了,這個執行緒什麼也沒有,要讓他先搞點事情。要讓他搞事情,就要重寫t的run 方法,我們應當把執行緒要做的所有工作都放在這個函式裡面,執行緒在這個方法裡面...