日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

synchronized原理_synchronized 底层原理与内存屏障

發布時間:2023/12/10 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 synchronized原理_synchronized 底层原理与内存屏障 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊?藍色“?深入原理”,關注并“設為星標”

技術干貨,第一時間推送

鎖概述

我們知道線程安全問題的產生前提是多個線程并發訪問共享變量、共享資源(以下統稱為共享數據)。于是,我們很容易想到保障線程安全的方法將多個線程對共享數據的并發訪問轉換為串行訪問,即一個共享數據一次只能被一個線程訪問,該線程訪問結束后其他線程才能對其進行訪問。鎖(Lock)就是利用這種思路以保障線程安全的線程同步機制。

按照上述思路,鎖可以理解為對共享數據進行保護的許可證。對于同一個許可證所保護的共享數據而言,任何線程訪問這些共享數據前必須先持有該許可證。一個線程只有在持有許可證的情況下才能夠對這些共享數據進行訪問;并且,一個許可證一次只能夠被一個線程持有;許可證的持有線程在其結束對這些共享數據的訪問后必須讓出(釋放)其持有的許可證,以便其他線程能夠對這些共享數據進行訪問。

一個線程在訪問共享數據前必須申請相應的鎖(許可證),線程的這個動作被稱為鎖的獲得(Acquire)。一個線程獲得某個鎖(?持有許可證?),我們就稱該線程為相應鎖的持有線程?(?線程持有許可證?),一個鎖一次只能被一個線程持有。鎖的持有線程可以對該鎖所保護的共享數據進行訪問,訪問結束后該線程必須釋放?( Release )?相應的鎖。鎖的持有線程在其獲得鎖之后和釋放鎖之前這段時間內所執行的代碼被稱為臨界區( Critical Section )。因此,共享數據只允許在臨界區內進行訪問,臨界區一次只能被一個線程執行。

鎖具有排他性(Exclusive),即一個鎖一次只能被一個線程持有。因此,這種鎖被稱為排他鎖或者互斥鎖?( Mutex )。這種鎖的實現方式代表了鎖的基本原理,如圖所示。?

按照Java虛擬機對鎖的實現方式劃分,Java 平臺中的鎖包括內部鎖( Intrinsic ?Lock )和顯式鎖?( Explicit Lock )。內部鎖是通過synchronized關鍵字實現的;顯式鎖是通過java.concurrent.locks.Lock接口的實現類(如 java.concurrent.locks.ReentrantLock 類?)?實現的。

鎖的作用

鎖能夠保護共享數據以實現線程安全,其作用包括保障原子性、保障可見性和保障有序性。

鎖是通過互斥保障原子性的。所謂互斥 ( Mutual Exclusion ),就是指一個鎖一次只能被一個線程持有。因此一個線程持有一個鎖的時候,其他線程無法獲得該鎖,而只能等待其釋放該鎖后再申請。這就保證了臨界區代碼一次只能夠被一個線程執行。因此,一個線程執行臨界區期間沒有其他線程能夠訪問相應的共享數據,這使得臨界區代碼所執行的操作自然而然地具有不可分割的特性,即具備了原子性。

從互斥的角度來看,鎖其實是將多個線程對共享數據的訪問由本來的并發( 未使用鎖的情況下 )改為串行( 使用鎖之后 )。因此,雖然實現并發是多線程編程的目標,但是這種并發往往是并發中帶有串行的局部并發。這好比公路維修使得多股車道在某處被合并成一股小車道,從而使原本在多股車道上并駕齊驅的車輛不得不“魚貫而行”。

我們知道,可見性的保障是通過寫線程沖刷處理器緩存和讀線程刷新處理器緩存這兩個動作實現的。在Java平臺中,鎖的獲得隱含著刷新處理器緩存這個動作,這使得讀線程在執行臨界區代碼前( 獲得鎖之后 ) 可以將寫線程對共享變量所做的更新同步到該線程執行處理器的高速緩存中;而鎖的釋放隱含著沖刷處理器緩存這個動作,這使得寫線程對共享變量所做的更新能夠被“推送” 到該線程執行處理器的高速緩存中,從而對讀線程可同步。因此,鎖能夠保障可見性。

鎖能夠保障有序性。寫線程在臨界區中所執行的一系列操作在讀線程所執行的臨界區看起來像是完全按照源代碼順序執行,即讀線程對這些操作的感知順序與源代碼順序一致。這是暫且對原子性和可見性的保障的結果。設寫線程在臨界區中更新了b 、c 和 flag 這 3 個共享變量,如下代碼片段所示 :

由于鎖對可見性的保障,寫線程在臨界區中對上述任何一個共享變量所做的更新都對讀線程可見。并且,由于臨界區內的操作具有原子性,因此寫線程對上述共事變量的更新會同時對讀線程可見,即在讀線程看來這些變量就像是在同一刻被更新的。因此讀線程并無法(也沒有必要)區分寫線程實際上是以什么順序更新上述變量的,這意味著讀線程可以認為寫線程是依照源代碼順序更新上述共享變量的,即有序性得以保障。

盡管鎖能夠保障有序性,但是這并不意味著臨界區內的內存操作不能夠被重排序。臨界區內的任意兩個操作依然可以在臨界區之內被重排序(即不會重排到臨界區之外)。由于臨界區內的操作具有的原子性,寫線程在臨界區內對各個共享數據的更新同時對讀線程可見,因此這種重排序并不會對其他線程產生影響。

在理解,以及使用鎖保證線程安全的時候,需要注意鎖對可見性、原子性和有序性的保障是有條件的,我們要同時保證以下兩點得以滿足。

? 這些線程在訪問同一組共享數據的時候必須使用同一個鎖。

? 這些線程中的任意一個線程,即使其僅僅是讀取這組共享數據而沒有對其進行更新的話,也需要在讀取時持有相應的鎖。

上述任意一個條件未滿足都會使原子性、可見性和有序性沒有保障。可見,我們說鎖能夠保護共享數據其實是一種“協議”?的結果,這個協議就是任何訪問該共享數據的寫線程、?讀線程都要滿足上述條件。只要有任何一個線程沒有遵守這個協議實際上就被打破,從而無法保障線程安全。這就好比交通規則(?“協議”?)?要靠人人都遵守才能保障交通安全一樣。

Java平臺中的任何一個對象都有唯一一個與之關聯的鎖。這種鎖被稱為監視器?( Monitor )?或者內部鎖?( Intrinsic Lock )。內部鎖是一種排他鎖,它能夠保障原子性、可見性和有序性。

內部鎖是通過synchronized關鍵字實現的。synchronized 關鍵字可以用來修飾方法以及代碼塊(?花括號?“?{?}?”?包裹的代碼?)。

synchronized關鍵字修飾的方法就被稱為同步方法( Synchronized Method )。synchronized ?修飾的靜態方法就被稱為同步靜態方法,synchronized ?修飾的實例方法就被稱為同步實例方法。同步方法的整個方法體就是一個臨界區。

synchronized關鍵字所引導的代碼塊就是臨界區。鎖句柄是一個對象的引用(它或者能夠返回對象的表達式)。例如,鎖句柄可以填寫為this關鍵字(?表示當前對象?)。習慣上我們也直接稱鎖句柄為鎖。鎖句柄對應的監視器就被稱為相應同步塊的引導鎖。相應地,我們稱呼相應的同步塊為該鎖引導的同步塊。

作為鎖句柄的變量通常采用final修飾。這是因為鎖句柄變量的值一旦改變,會導致執行同一個同步塊的多個線程實際上使用不同的鎖,從而導致競態。有鑒于此,通常我們會使用 private 修飾作為鎖句柄的變量。

線程在執行臨界區代碼的時候必須持有該臨界區的引導鎖。一個線程執行到同步塊(同步方法也可看作同步塊)時必須先申請該同步塊的引導鎖,只有申請成功(獲得)該鎖的線程才能夠執行相應的臨界區。一個線程執行完臨界區代碼后引導該臨界區的鎖就會被自動釋放。在這個過程中,線程對內部鎖的申請與釋放的動作由Java虛擬機負責代為實施,這也正是 synchronized 實現的鎖被稱為內部鎖的原因。

內部鎖的使用并不會導致鎖世漏。這是因為Java編譯器?( javac )?在將同步塊代碼編譯為字節碼的時候,對臨界區中可能拋出的而程序代碼中又未捕獲的異常進行了特殊(?代為?)處理,這使得臨界區的代碼即使拋出異常也不會妨礙內部鎖的釋放。

內部鎖的調度

Java虛擬機會為每個內部鎖分配一個入口集?( Entry ?Set ),用于記錄等待獲得相應內部鎖的線程。多個線程申請同一個鎖的時候,只有一個申請者能夠成為該鎖的持有線程(?即申請鎖的操作成功?),而其他申請者的申請操作會失敗。這些申請失敗的線程并不會拋出異常,而是會被暫停(?生命周期狀態變為 BLOCKED )?并被存入相應鎖的入口集中等待再次申請鎖的機會?。入口集中的線程就被稱為相應內部鎖的等待線程。當這些線程申請的鎖被其持有線程釋放的時候,該鎖的入口集中的一個任意線程會被Java虛擬機喚醒,從而得到再次申請鎖的機會。由于Java 虛擬機對內部鎖的調度僅支持非公平調度,被喚醒的等待線程占用處理器運行時可能還有其他新的活躍線程?(?處于RUNNABLE 狀態,且未進入過入口集?)?與該線程搶占這個被釋放鎖,因此被喚醒的線程不一定就能成為該鎖的持有線程。另外,Java 虛擬機如何從一個鎖的入口集中選擇一個等待線程,作為下一個可以參與再次申請相應鎖的線程,這個細節與 Java 虛擬機的具體實現有關:這個被選中的線程有可能是入口集中等待時間最長的線程,也可能是等待時間最短的線程,或者完全是隨機的一個線程。因此,我們不能依賴這個具體的選擇算法。

前文我們講解鎖是如何保證可見性的時候提到了線程獲得和釋放鎖時所分別執行的兩個動作:刷新處理器緩存和沖刷處理器緩存。對于同一個鎖所保護的共享數據而言,前一個動作保證了該鎖的當前持有線程能夠讀取到前一個持有線程對這些數據所做的更新,后一個動作保證了該鎖的持有線程對這些數據所做的更新對該鎖的后續持有線程可見。那么,這兩個動作是如何實現的呢?弄清楚這個問題有助于我們學習和掌握包括鎖在內的所有Java線程同步機制?。

Java虛擬機底層實際上是借助內存屏障( Memory Barrier ,也稱 Fence )來實現上述兩個動作的。內存屏障是對一類僅針對內存讀、寫操作指令?( Instruction )?的跨處理器架構?(?比如 x86 、ARM )的比較底層的抽象(?或者稱呼?)。內存屏障是被插入到兩個指令之間進行使用的,其作用是禁止編譯器、處理器重排序從而保障有序性。它在指令序列?(?如指令 1 ;指令2 ;指令3 )中就像是一堵墻?(?因此被稱為屏障?)一樣使其兩側?(?之前和之后?)的指令無法“穿越”它?(?一旦穿越了就是重排序了?)。但是,為了實現禁止重排序的功能,這些指令也往往具有一個副作用刷新處理器緩存、沖刷處理器緩存,從而保證可見性。不同微架構的處理器所提供的這樣的指令是不同的,并且出于不同的目的使用的相應指令也是不同的。例如對于?“寫-寫”?(?寫后寫?)?操作,如果僅僅是為了防止?(?禁止?)?重排序而對可見性保障沒有要求,那么在x86架構的處理器下使用空操作就可以了(??因為 x86處理器不會對?“寫-寫”?操作進行重排序?)。而如果對可見性有要求(比如前一個寫操作的結果要在后一個寫操作執行前對其他處理器可見),那么在x86 ???處理器下需要使用LOCK 前綴指令或者sfence 指令、mfence 指令;在 ARM 處理器下則需要使用 DMB 指令。

按照內存屏障所起的作用來劃分,將內存屏障劃分為以下幾種。

按照可見性保障來劃分。內存屏障可分為加載屏障(Load Barrier)和存儲屏障(Store ???? Barrier)。加載屏障的作用是刷新處理器緩存,存儲屏障的作用沖刷處理器緩存。Java虛擬機會在 MonitorExit (?釋放鎖?)?對應的機器碼指令之后插入一個存儲屏障,這就保障了寫線程在釋放鎖之前在臨界區中對共享變量所做的更新對讀線程的執行處理器來說是可同步的。相應地,Java 虛擬機會在 MonitorEnter (?申請鎖?)?對應的機器碼指令之后臨界區開始之前的地方插入一個加載屏障,這使得讀線程的執行處理器能夠將寫線程對相應共享變量所做的更新從其他處理器同步到該處理器的高速緩存中。因此,可見性的保障是通過寫線程和讀線程成對地使用存儲屏障和加載屏障實現的。

按照有序性保障來劃分,內存屏障可以分為獲取屏障(Acquire Barrier)和釋放屏障?( Release Barrier )。獲?取?屏?障?的?使?用?方?式?是?在?一?個?讀?操?作?(?包括 Read-Modify-Write 以及普通的讀操作?)之后插入該內存屏障,其作用是禁止該讀操作與其后的任何讀寫操作之間進行重排序,這相當于在進行后續操作之前先要獲得相應共享數據的所有權?(?這也是該屏障的名稱來源?)。釋放屏障的使用方式是在一個寫操作之前插入該內存屏障,其作用是禁止該寫操作與其前面的任何讀寫操作之間進行重排序。這相當于在對相應共享數據操作結束后釋放所有權(?這也是該屏障的名稱來源?)。?Java虛擬機會在 MonitorEnter(?它包含了讀操作?)?對應的機器碼指令之后臨界區開始之前的地方插入一個獲取屏障,并在臨界區結束之后 MonitorExit (?它包含了寫操作?)?對應的機器碼指令之前的地方插入一個釋放屏障。因此,這兩種屏障就像是三明治的兩層面包片把火腿夾住一樣把臨界區中的代碼(指令序列)包括起來,如圖所示。

由于獲取屏障禁止了臨界區中的任何讀、寫操作被重排序到臨界區之前的可能性。而釋放屏障又禁止了臨界區中的任何讀、寫操作被重排序到臨界區之后的可能性。因此臨界區內的任何讀、寫操作都無法被重排序到臨界區之外。在鎖的排他性的作用下,這使得臨界區中執行的操作序列具有原子性。因此,寫線程在臨界區中對各個共享變量所做的更新會同時對讀線程可見,即在賣線程看來各個共享變量就像是“一下子”?被更新的,于是這些線程無從?(?也無必要?)?區分這些共享變量是以何種順序被更新的。這使得寫線程在臨界區中執行的操作自然而然地具有有序性讀線程對這些操作的感知順序與源代碼順序一致。

可見,鎖對有序性的保障是通過寫線程和讀線程配對使用釋放屏障與加載屏障實現的。

https://www.jianshu.com/p/39ecb11d41d7

-深入原理- ?

? ?知其然并知其所以然? ??

總結

以上是生活随笔為你收集整理的synchronized原理_synchronized 底层原理与内存屏障的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。