国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java并發編程——線程安全性深層原因

Faremax / 2525人閱讀

摘要:線程安全性深層原因這里我們將會從計算機硬件和編輯器等方面來詳細了解線程安全產生的深層原因。類似這種不影響單線程語義的亂序執行我們稱為指令重排。通過線程安全性深層原因我們能更好的理解這三大性質的根本性原因。上一篇并發編程線程基礎查漏補缺

線程安全性深層原因

這里我們將會從計算機硬件和編輯器等方面來詳細了解線程安全產生的深層原因。

緩存一致性問題 CPU內存架構

隨著CPU的發展,而因為CPU的速度和內存速度不匹配的問題(CPU寄存器的訪問速度非??欤鴥却嬖L問速度相對偏慢),所有在CPU和內存之間出現了多級高速緩存。下圖是現代CPU和內存的一般架構圖:

我們可以看到高速緩存也分為三級緩存,越靠近寄存器的級別緩存訪問速度越快。其中L3 Cache為多核共享的,L1和L2 Cache為單核獨享,而L1又有數據緩存(L1 d)和指令緩存(L1 i)。

正因為高速緩存的出現,各CPU內核從主內存獲取相同的數據將會存在于緩存中,當多核都對此數據進行操作并修改值,此時另外的核心并不知道此值已被其他核心修改,從而出現緩存不一致的問題。

如何解決緩存一致性問題

解決緩存一致性問題一般有兩個方法:

第一個是采用總線鎖,在總線級別加鎖,這樣從內存種訪問到的數據將被當個CPU核心獨占,在多核的情況下對單個資源將是串行化的。這種方式性能上將大打折扣。

第二個是采用緩存鎖,在緩存的級別上進行加鎖。此種方式需要某種協議對緩存行數據進行同步,后面所說的緩存一致行協議便是一種實現。

緩存一致性協議(MESI)

為了解決緩存一致性的問題,一些CPU系列(比如Intel奔騰系列)采用了MESI協議來解決緩存一致性問題。此協議將每個緩存行(Cache Line)使用4種狀態進行標記。

M: 被修改(Modified)

該緩存行只被緩存在該CPU核心的緩存中,并且是被修改過的(dirty),即與主存中的數據不一致,該緩存行中的內存需要在未來的某個時間點(允許其它CPU讀取請主存中相應內存之前)寫回(write back)主存。當被寫回主存之后,該緩存行的狀態會變成獨享(exclusive)狀態。

E: 獨享的(Exclusive)

該緩存行只被緩存在該CPU核心緩存中,它是未被修改過的(clean),與主存中數據一致。該狀態可以在任何時刻當有其它CPU核心讀取該內存時變成共享狀態(shared)。同樣地,當CPU核心修改該緩存行中內容時,該狀態可以變成Modified狀態。

S: 共享的(Shared)

該狀態意味著該緩存行可能被多個CPU緩存,并且各個緩存中的數據與主存數據一致(clean),當有一個CPU修改該緩存行中,其它CPU中該緩存行可以被作廢(變成無效狀態(Invalid))。

I: 無效的(Invalid)

該緩存是無效的(可能有其它CPU核心修改了該緩存行)

在MESI協議中,每個CPU核心的緩存控制器不僅知道自己的操作(local read和local write),每個核心的緩存控制器通過監聽也知道其他CPU中cache的操作(remote read和remote write),再確定自己cache中共享數據的狀態是否需要調整。

local read(LR):讀本地cache中的數據;

local write(LW):將數據寫到本地cache;

remote read(RR):其他核心發生read;

remote write(RW):其他核心發生write;

針對操作,緩存行的狀態遷移圖如下:

指令重排序問題

在我們編程過程中,習慣性程序思維認為程序是按我們寫的代碼順序執行的,舉個例子來說,某個程序中有三行代碼:

int a = 1; // 1
int b = 2; // 2
int c = a + b; // 3

從程序員角度執行順序應該是1 -> 2 -> 3,實際經過編譯器和CPU的優化很有可能執行順序會變成 2 -> 1 -> 3(注意這樣的優化重排并沒有改變最終的結果)。類似這種不影響單線程語義的亂序執行我們稱為指令重排。(后面講Java內存模型也會講到這部分。)

編譯器指令重排

舉個例子,我們先看可以看一段代碼:

class ReorderExample {  
    int a = 0;  
    boolean flag = false;  
    public void write() {  
        a = 1;                     // 1  
        flag = true;               // 2  
    }
  
    public void read() {  
        if (flag) {                // 3  
            int i =  a * a;        // 4  
        }
    }
}

在單線程的情況下如果先write再read的話,i的結果應該是1。但是在多線程的情況下,編譯器很可能對指令進行重排,有可能出現的執行順序是2 -> 3 -> 4 -> 1。這個時候的i的結果就是0了。(1和2之間以及3和4之間不存在數據依賴,有關數據依賴在后面的Java內存模型中會講到。)

CPU指令重排

在CPU層面,一條指令被分為多個步驟來執行,每個步驟會使用不同的硬件(比如寄存器、存儲器、算術邏輯單元等)。執行多個指令時采用流水線技術進行執行,如下示意圖:

注意這里出現的”停頓“,出現這個原因是因為步驟22需要步驟13得到結果后才能進行。CPU為了進一般優化:消除一些停頓,這時會將指令3(指令3對指令2和1都沒有數據依賴)移到指令2之前進行運行。這樣就出現了指令重排,根本原因是為了優化指令的執行。

內存系統重排

CPU經過長時間的優化,在寄存器和L1緩存之間添加了LoadBuffer、StoreBuffer來降低阻塞時間。LoadBuffer、StoreBuffer,合稱排序緩沖(Memoryordering Buffers (MOB)),Load緩沖64長度,store緩沖36長度,Buffer與L1進行數據傳輸時,CPU無須等待。

CPU執行load讀數據時,把讀請求放到LoadBuffer,這樣就不用等待其它CPU響應,先進行下面操作,稍后再處理這個讀請求的結果。

CPU執行store寫數據時,把數據寫到StoreBuffer中,待到某個適合的時間點,把StoreBuffer的數據刷到主存中。

因為StoreBuffer的存在,CPU在寫數據時,真實數據并不會立即表現到內存中,所以對于其它CPU是不可見的;同樣的道理,LoadBuffer中的請求也無法拿到其它CPU設置的最新數據;由于StoreBuffer和LoadBuffer是異步執行的,所以在外面看來,先寫后讀,還是先讀后寫,沒有嚴格的固定順序。

由于引入StoreBuffer和LoadBuffer導致異步模式,從而導致內存數據的讀寫可能是亂序的(也就是內存系統的重排序)。

內存屏障

為了解決CPU優化帶來的不可見、重排序的問題,可以使用內存屏障(memory barrier)來阻止一定的優化(在后面介紹Java內存模型也會詳細結合講內存屏障)。不同的CPU架構對內存屏障的實現方式與實現程度非常不一樣,下面我們看下X86架構中內存屏障的實現。

Store Barrier

使所有Store Barrier之前發生的內存更新都是可見的。

Load Barrier

使所有Store Barrier之前發生的內存更新,對Load Barrier之后的load操作都是可見的。

Full Barrier

所有Full Barrier之前發生的操作,對所有Full Barrier之后的操作都是可見的。

延伸

在程序我們常說的三大性質:可見性、原子性、有序性。通過線程安全性深層原因我們能更好的理解這三大性質的根本性原因。(可見性、原子性、有序性會在后面文章中進行詳細講解。)

上一篇:Java并發編程——線程基礎查漏補缺

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72746.html

相關文章

  • 當我們在說“并發、多線程”,說的是什么?

    摘要:兜底任務,處理數據不一致狀態的任務。什么是多線程多線程是并發的一種重要形式。通過具體的多線程問題引出多線程編程中的關鍵點和對應的工具與知識點,輕松學會多線程編程。 這篇文章的目的并不是想教你如何造火箭(面試造火箭,工作擰螺絲),而是想通過對原理和應用案例的有限度剖析來協助你構建起并發的思維,并將操作系統的理論知識與工程實踐結合起來,貫穿從學到會的全過程。當然,雖然我們是從實用角度出發,...

    sf_wangchong 評論0 收藏0
  • 第10章:并發和分布式編程 10.1并發性和線程全性

    摘要:并發模塊本身有兩種不同的類型進程和線程,兩個基本的執行單元。調用以啟動新線程。在大多數系統中,時間片發生不可預知的和非確定性的,這意味著線程可能隨時暫?;蚧謴?。 大綱 什么是并發編程?進程,線程和時間片交織和競爭條件線程安全 策略1:監禁 策略2:不可變性 策略3:使用線程安全數據類型 策略4:鎖定和同步 如何做安全論證總結 什么是并發編程? 并發并發性:多個計算同時發生。 在現代...

    instein 評論0 收藏0
  • 線程安全

    摘要:不可變在中,不可變的對象一定是線程安全的。在里標注自己是線程安全的類,大多都不是絕對線程安全,比如某些情況下類在調用端也需要額外的同步措施。無同步方案要保證線程安全,不一定就得需要數據的同步,兩者沒有因果關系。 在之前學習編程的時候,有一個概念根深蒂固,即程序=算法+數據結構。數據代表問題空間中的客體,代碼就用來處理這些數據,這種思維是站在計算機的角度去抽象問題和解決問題,稱之為面向過...

    fuyi501 評論0 收藏0
  • 并發 - 收藏集 - 掘金

    摘要:在中一般來說通過來創建所需要的線程池,如高并發原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細原理解析 - 后端 - 掘金今天我們來研究學習一下AbstractQueuedSynchronizer類的相關原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...

    levius 評論0 收藏0
  • 并發 - 收藏集 - 掘金

    摘要:在中一般來說通過來創建所需要的線程池,如高并發原理初探后端掘金閱前熱身為了更加形象的說明同步異步阻塞非阻塞,我們以小明去買奶茶為例。 AbstractQueuedSynchronizer 超詳細原理解析 - 后端 - 掘金今天我們來研究學習一下AbstractQueuedSynchronizer類的相關原理,java.util.concurrent包中很多類都依賴于這個類所提供的隊列式...

    fantix 評論0 收藏0

發表評論

0條評論

Faremax

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<