<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:從零開始設(shè)計個JMM吧,說說你的思路

          共 11581字,需瀏覽 24分鐘

           ·

          2023-10-31 22:27

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進(jìn)!你不來,我和你的競爭對手一起精進(jìn)!

          編輯:業(yè)余草

          來源:juejin.cn/post/7278584104852455463

          推薦:https://t.zsxq.com/136Bz0EvG

          自律才能自由

          相信大多數(shù)同學(xué)都背過JMM的八股,一聽到JMM直接開始吟唱:什么重排序,線程本地內(nèi)存與主存,巴拉巴拉的。但「從零開始設(shè)計個JMM吧,說說你的思路」顯然不是只問「Java是怎么做的」,更加強(qiáng)調(diào):「你要設(shè)計JMM,為什么需要JMM?要解決哪些問題?怎么解決的?」 這也是本篇文章要強(qiáng)調(diào)的,我們只有明白為什么,才能更好地理解JMM定義的規(guī)范。

          為什么需要JMM

          我們需要并發(fā)編程。比如看一個視頻,計算機(jī)需要做:接受并讀取數(shù)據(jù)包,解壓數(shù)據(jù)包,播放數(shù)據(jù)包三件事。我們當(dāng)然希望有三個線程分別取執(zhí)行這三個子任務(wù),從而達(dá)到邊收數(shù)據(jù),邊解壓,邊播放,而不是先接受完整的數(shù)據(jù)包,再解壓完,再播放這樣的串行執(zhí)行。但我們要解決這樣一個問題:「如果播放的速度快于解壓的速度,那么負(fù)責(zé)播放的線程如何感知到此時能播放的數(shù)據(jù)已經(jīng)播放完了?解壓得到了新的數(shù)據(jù),又該如何通知播放線程可以播放了呢?」

          通信與同步的概念

          用更抽象的語言來表達(dá)就是:

          • 線程之間如何「通信」,更確切的說,如何將線程A的數(shù)據(jù)交付給線程B,「線程之間以何種機(jī)制來交換信息」
          • 線程之間如何「同步」,即線程A如何讓線程B停下來,等線程A的工作完成到一定程度再把線程B叫醒繼續(xù)工作

          這就是「通信與同步的概念」。這就是并發(fā)編程需要實(shí)現(xiàn)的兩個最基本的功能:通信與同步。

          并發(fā)編程基本工作模型

          那么實(shí)現(xiàn)并發(fā)編程的核心問題就是:如何實(shí)現(xiàn)「通信與同步」

          一般有兩種并發(fā)編程的基本工作模型:

          • 消息傳遞并發(fā)模型:線程A給線程B發(fā)送數(shù)據(jù)消息,從而通信,收到數(shù)據(jù)的線程B可以根據(jù)數(shù)據(jù)來決定是否阻塞(停下來)或是繼續(xù)工作。即:「顯式通信,隱式同步」
          • 共享內(nèi)存并發(fā)模型:線程A修改共享內(nèi)存中的某個數(shù)據(jù),B可以通過某種方式,比如輪詢,感知到這個數(shù)據(jù)的變化,從而改變自己的行為。即:「隱式通信,顯式同步 (這也是JAVA所采用的模型)」

          重新審視面試題:線程之間如何通信

          不要急著開始吟唱:比如wait/notify之類的。

          首先wait源碼是將線程放入monitor的WaitSet中阻塞,這個過程和別的線程交換信息了嗎?其實(shí)沒有,但調(diào)用wait方法會「釋放鎖」。

          而「釋放鎖」這個行為,對開發(fā)者來說,是同步,但會隱式地將線程本地內(nèi)存的數(shù)據(jù)回寫到主存中,開發(fā)者感知不到,但是實(shí)打?qū)嵶隽耍@是synchronized的內(nèi)存語義保證的,這就是「隱式通信,顯式同步」。

          因此,在Java世界中,是「隱式通信,顯式同步」的,在吟唱之前,先和面試官說清楚這個概念,是非常有必要的,然后再繼續(xù)你的吟唱。別小看這么一個簡單的問題,這就是你和其他競爭者拉開差距的好機(jī)會。

          下層給并發(fā)編程帶來的問題

          雖然上述分析已經(jīng)讓我們明確了實(shí)現(xiàn)并發(fā)編程最關(guān)鍵的問題所在,也確定了兩種模型來實(shí)現(xiàn)線程間的通信與同步功能,但我們還必須考慮到下層的硬件,操作系統(tǒng),編譯器等向上的影響。硬件和操作系統(tǒng)的實(shí)現(xiàn)各不相同,我們需要一個模型來「屏蔽下層對上層的影響,或者說提供一些手段讓程序員一定程度上能夠影響到下層硬件的工作」。而一般來說,下層對上層的影響就是如下三個問題,它們也被叫做并發(fā)三要素:

          可見性問題

          可見性問題一般是由CPU緩存等硬件引起,每個核心的L1,L2 cache都是私有的,每個線程分別運(yùn)行在CPU的兩個核心,對cache的寫操作,硬件不能保證彼此的可見性,這就是并發(fā)模型需要考慮的第一個問題:可見性問題。

          原子性問題

          CPU如何調(diào)度線程?CPU會為每個線程分配一個時間片,執(zhí)行完這個時間片,該線程還沒執(zhí)行完,會先去執(zhí)行其他的線程。那這會導(dǎo)致什么問題呢?假設(shè)我們此時有兩個線程A,B都執(zhí)行同一行代碼:i++,起初i為0,我們預(yù)期執(zhí)行結(jié)果為i = 2,而這行代碼實(shí)際需要三條 CPU 指令:

          • 讀取i的值到寄存器
          • +1,此時結(jié)果存儲在寄存器
          • 寫回,可能寫入的是CPU Cache,也可能是主存

          假設(shè)線程A讀取i后阻塞,輪到線程B執(zhí)行,線程B正常執(zhí)行完i++,并且順利寫回主存,此時主存的i為1,輪到線程A執(zhí)行,但此時A「不會重新去讀取主存中的i的值」,繼續(xù)執(zhí)行+1,并且順利寫回主存,此時主存的i的值仍為1,這就和我們所預(yù)想的i = 2 不太相同。這就是第二個問題:原子性問題

          ?

          可以看到,我們的假設(shè)已經(jīng)保證了每條線程都從主存中讀取數(shù)據(jù),并且寫完立刻寫回主存,即我們的假設(shè)是「已經(jīng)解決了可見性問題」的,但因為CPU調(diào)度導(dǎo)致的「原子性問題」沒有解決,才導(dǎo)致了程序運(yùn)行結(jié)果與預(yù)期不同

          ?

          具有原子性的一個或多個操作叫做原子操作,「原子操作是不能操作系統(tǒng)被中斷的」

          有序性問題

          從 java 源代碼到最終實(shí)際執(zhí)行的指令序列,會分別經(jīng)歷下面三種重排序:

          重排序的概念:計算機(jī)在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序。

          舉個例子:

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

          上面1 2行代碼,它們的執(zhí)行的先后順序,對第三行代碼而言,根本不重要,即1 2行即使交換執(zhí)行順序,也不會影響程序的運(yùn)行結(jié)果,因此在這里就有一個可重排序的空間,如果重排序能帶來性能的提高,那也許就會發(fā)生重排序

          那么在一個線程內(nèi),以及在多個線程并發(fā)執(zhí)行的情況,硬件,編譯器具體如何重排序都可能不一樣,因此指令的執(zhí)行順序?qū)Τ绦騿T而言是個黑盒,我們不得而知。為了使程序的執(zhí)行結(jié)果符合預(yù)期,我們必須對這些重排序做一些限制,從而使得多線程并發(fā)的場景下,程序的執(zhí)行結(jié)果仍然與預(yù)期相符,這就是第三個問題:有序性問題。

          「可見性和有序性的區(qū)別」

          還是舉個例子來幫助你理解:

          仍然是兩個線程A和B,A執(zhí)行代碼:i++,起初i為0;B僅僅是讀取 i 的值。按照預(yù)期,A先執(zhí)行i++,并刷新到主存,B讀取 i 應(yīng)該是1。但實(shí)際上,盡管你先啟動A線程,也可能被重排序,CPU先執(zhí)行線程B,讀到i為0,再執(zhí)行線程A做i++。這個過程沒有發(fā)生可見性問題,因為在B讀取的時候,主存,以及A的本地內(nèi)存 i 都是0,但是運(yùn)行結(jié)果與預(yù)期不相符。

          并發(fā)編程基礎(chǔ)小結(jié)

          學(xué)到這里,我們已經(jīng)知道實(shí)現(xiàn)并發(fā)編程的基本理論,知道通信與同步的概念以及并發(fā)必須解決的三大問題:可見性,原子性,有序性。那接下來就是:如何實(shí)現(xiàn)通信與同步的功能,以及如何解決三大問題了。雖然說是「從零開始設(shè)計JMM」,但最終肯定還是要說到JMM的實(shí)現(xiàn)啊,問就是我和JMM設(shè)計者想的一樣。

          接下來我們就正式來看一看Java是如何做的。

          JMM:Java內(nèi)存模型??

          JMM :Java Memory Model,中文叫 Java內(nèi)存模型,JMM描述的是一組規(guī)范

          下面依次介紹JMM規(guī)范

          解決可見性:內(nèi)存的抽象劃分

          • JMM決定了一個線程對共享變量的寫入何時對另一個線程可見
          • JMM定義了線程和主內(nèi)存之間的抽象關(guān)系(線程本地內(nèi)存和主存)
          • JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為Java程序員提供內(nèi)存可見性保證

          每個線程創(chuàng)建時,JVM都會為其分配「線程本地內(nèi)存」,而用「主存」代表線程共享的所有資源。「線程對共享變量的所有操作都必須在自己的「線程本地內(nèi)存」中進(jìn)行,不能直接從「主存」中讀取。「線程本地內(nèi)存」是線程私有的,別的線程無法訪問,注意:這不代表「線程本地內(nèi)存」內(nèi)的變量別的線程無法擁有。兩個「線程本地內(nèi)存」可以獲得來自「主存」的一模一樣的變量的副本」。見下圖:

          線程本地內(nèi)存的本質(zhì)

          線程本地內(nèi)存是JMM提出的一個「抽象的概念,并不真實(shí)存在」。它涵蓋了「緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。」 因為硬件這么多,實(shí)現(xiàn)各不相同,依賴于硬件是不可靠的,因此需要這一個抽象的概念來表示:「所有由于某種原因?qū)е戮€程操作的數(shù)據(jù)不會立即寫回到內(nèi)存,或是通知其它CPU核心,從而造成一致性問題的潛在因素的集合」

          ?

          雖然解決CPU緩存一致性有MESI協(xié)議,但實(shí)際上,上層語言不能完全依賴于硬件提供的某些功能,假如某CPU不支持該協(xié)議呢?像解決CPU緩存一致性的協(xié)議還有:MSI、MOSI、和 Dragon Protocol等等。那Java并發(fā)編程在這個硬件上就毫無意義。因此需要JMM來定義這樣的規(guī)范,從而「屏蔽硬件的不可靠性」

          ?

          關(guān)于本地內(nèi)存可能的疑惑

          ?

          你可能會問:Java線程持有堆對象的引用,會直接操作堆嗎

          雖然Java采用直接指針的方式訪問對象,但實(shí)際上「并不會直接操作堆對象」,畢竟堆也屬于內(nèi)存,也就是JMM的主存區(qū)域,Java線程的做法是:「將堆對象拷貝到線程自己的本地內(nèi)存來進(jìn)行操作」(如果對象很大只會拷貝一部分)。本地內(nèi)存確實(shí)是線程私有的,但請不要和「JVM運(yùn)行時數(shù)據(jù)結(jié)構(gòu)的那些線程私有的棧等」劃上等號,本地內(nèi)存是抽象的。

          ?

          線程本地內(nèi)存小結(jié)

          一個線程無論操作什么數(shù)據(jù),包括修改堆對象的某個字段,都是在自己的「線程本地內(nèi)存」上操作的,且不能直接操作主存。并且線程的本地內(nèi)存是線程私有的,其它線程訪問不到。本地內(nèi)存是抽象的概念。

          這樣規(guī)范的原因是:硬件提供緩存一致不可靠,那我們只能認(rèn)為線程對數(shù)據(jù)的所有操作都是臟的(線程私有的棧等除外,因為這些內(nèi)容根本不會寫入主存),因為從本地內(nèi)存寫回到主存的時機(jī)我們不知道。

          解決有序性:禁止重排序

          JMM重排序的策略

          JMM確保在不同的編譯器和不同的處理器平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的「內(nèi)存可見性」保證。

          讀完這句話,會有疑惑嗎?為什么明明禁止重排序是解決有序性的,怎么這個作者寫了可見性?實(shí)際上,「JMM對重排序的策略是:在不改變程序執(zhí)行結(jié)果的前提下,你愛怎么重排序就怎么重排序。即:JMM不保證程序執(zhí)行的絕對有序性。」 因此,JMM只處理那些「會導(dǎo)致程序執(zhí)行結(jié)果改變的有序性問題」,其他的不管。那保證了部分有序有什么用?那當(dāng)然是為了可見性咯。很多場景下,代碼之間存在依賴關(guān)系。這就像類加載器,如果沒有bootstrap,那ext啥也不是,因為ext類加載器的加載依賴于bootstrap。因此,存在依賴關(guān)系的代碼段,不保證有序性根本就沒辦法保證可見性,為了解決可見性所做的一切努力都是白費(fèi)。

          ?

          那么JMM如何確定「什么樣的重排序會導(dǎo)致程序執(zhí)行結(jié)果改變」呢?實(shí)際上,這個問題作為開發(fā)者而言不需要關(guān)心,JMM貼心的提供了happens-before原則,會在后文詳細(xì)介紹

          ?

          有序性問題的根本:可見性

          有序性問題的根本還是可見性,即:如果保證了可見性,有序無序又有什么關(guān)系呢?我們來舉個例子:

          int a = 1;
          int b = 2;

          看這兩行代碼,如果進(jìn)行了重排序,導(dǎo)致行2先于行1執(zhí)行,「不會對程序的執(zhí)行結(jié)果造成任何影響」。這就是為啥JMM的策略不是直接放棄重排序而保證絕對有序,此時:「程序無序執(zhí)行根本不是問題」

          int a = 1;
          System.out.print(a);

          再看這兩行代碼,我們期望輸出值“1”。如果進(jìn)行了重排序,導(dǎo)致行2先于行1執(zhí)行,但是行2仍然能夠正確輸出a為1,那么此時的有序性就不是問題。那如何保證行2在行1執(zhí)行前能夠看到a的值為1呢?「那就是:保證行1先于行2執(zhí)行,即保證程序執(zhí)行的有序性,這是保證可見性的前提。」

          • 「只要我們保證行1對行2的可見性,即行2能看到行1的修改,那有序無序無所謂;」
          • 「而要能夠讓行2看到行1的修改,我們需要讓行1先于行2執(zhí)行,即保證有序性。」

          因此:解決有序性問題,是為了解決有序性背后的可見性問題,這也是為什么JMM提供的happens-before原則,并不會強(qiáng)調(diào)有序性,而是強(qiáng)調(diào)內(nèi)存可見性。

          現(xiàn)在你應(yīng)該對有序性問題有了一個比較徹底的認(rèn)識了,我們來看看JMM限制重排序的具體規(guī)則:

          JMM限制重排序的具體規(guī)則

          我們之前說過:限制部分重排序需要解決:「如何確定什么樣的重排序會導(dǎo)致程序執(zhí)行結(jié)果改變」這樣一個問題。如果將這個問題拋給開發(fā)者,會是一件令人十分頭疼的事情,因此JMM提供了happens-before規(guī)則。

          JMM保障:只要你按照happens-before規(guī)則寫代碼,就能寫出「執(zhí)行結(jié)果與你預(yù)期相符」的代碼,不需要關(guān)心會發(fā)生什么重排序以及如何限制重排序等等問題。

          happens-before規(guī)則

          happens-before規(guī)則是面向程序員的,這個規(guī)則屏蔽了對多個編譯器和處理器的具體重排序規(guī)則,更強(qiáng)調(diào)結(jié)果,后續(xù)部分內(nèi)容將happens-before簡寫為h-b。

          happens-before關(guān)系的定義如下:

          1. 如果一個操作happens-before另一個操作,那么「第一個操作的執(zhí)行結(jié)果將對第二個操作可見」
          2. 兩個操作之間存在happens-before關(guān)系,并不意味著Java平臺的具體實(shí)現(xiàn)必須要按照happens-before關(guān)系指定的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關(guān)系來執(zhí)行的結(jié)果一致,那么JMM也允許這樣的重排序。

          可以看到,happens-before規(guī)則保證的是可見性,而不保證有序性。

          ?

          盡管你也可以理解成happens-before規(guī)則保證了有序性,多了這樣一個假設(shè),不會對你的代碼的執(zhí)行結(jié)果有任何影響,但事實(shí)上happens-before就是沒有保證絕對有序性,只通過保證部分有序性再保證可見性

          ?

          「happens-before的具體規(guī)則如下:」

          • 「一個線程中的每個操作」,先執(zhí)行的操作 happen-before 后執(zhí)行的操作
          • 監(jiān)視器鎖的解鎖happens-before加鎖
          • 對一個volatile域的寫,happens-before于任意后續(xù)對這個volatile域的讀
          • thread.start方法happens-before該線程的所有操作
          • 在線程A中調(diào)用B.join(),happens-before線程A后續(xù)的所有操作
          • interrupt方法happens-before Thread.isInterrupted 之前
          • happens-before具有傳遞性

          上述規(guī)則中,只有第一條是針對一個線程內(nèi)的,其余規(guī)則都適用于多線程的場景。

          happens-before既可以理解為JMM的一種規(guī)范,即上述所有規(guī)則的集合,也可以理解為兩個操作之間的一種關(guān)系,即h-b規(guī)則的集合的某一條,A happens-before B就代表A的所有操作對B可見。

          as if serial:單線程語義的保證

          「as if serial只針對單線程」,具體是:「一個線程內(nèi)的指令,雖然允許重排序,但是保證執(zhí)行結(jié)果唯一,且與沒有任何重排序的執(zhí)行結(jié)果完全一致。」

          happens-before理解實(shí)戰(zhàn)

          下面舉一些例子來幫助你徹底理解happens-before原則,如果答案和你預(yù)想的不一樣,說明你對happens-before原則的掌握還不夠透徹。

          h-b與CPU調(diào)度的混淆

          看這樣一個場景,請問輸出的a會是0還是1?

          static volatile int a;
          // 線程A
          new Thread(() -> {
              System.out.println(a);
          }).start();
          // 線程B
          new Thread(() -> {
              a = 1;
          }).start();

          答案是并不確定。你會說:誒呀,不是規(guī)定了「對volatile域的happens-before 對volatile域的讀,所以線程B的所有操作對線程A可見嘛」?難道這原則有問題?別忘了CPU的調(diào)度,CPU先給讀線程分配時間片,此時就讀到了0。那這個happens-before規(guī)則到底規(guī)定了啥呢?一旦CPU先給寫線程分配了時間片,執(zhí)行完了a = 1這條寫操作以后,任何的讀操作都能讀到 a 為1,而不會出現(xiàn)可見性問題。

          到這里,相信你對happens-before原則了解的差不多了,那其實(shí)對JMM解決有序性的問題就了解的比較透徹了。我們再來看一些別的問題。

          禁止重排序的方法:內(nèi)存屏障

          JMM具體是如何限制重排序的?內(nèi)存屏障:memory barriers,用于禁止某些指令重排序

          JMM一共有如下四種內(nèi)存屏障:

          屏障類型 指令示例 說明
          LoadLoad Barriers Load1; LoadLoad; Load2 確保 Load1 數(shù)據(jù)的裝載,之前于 Load2 及所有后續(xù)裝載指令的裝載。
          StoreStore Barriers Store1; StoreStore; Store2 確保 Store1 數(shù)據(jù)對其他處理器可見(刷新到內(nèi)存),之前于 Store2 及所有后續(xù)存儲指令的存儲。
          LoadStore Barriers Load1; LoadStore; Store2 確保 Load1 數(shù)據(jù)裝載,之前于 Store2 及所有后續(xù)的存儲指令刷新到內(nèi)存。
          StoreLoad Barriers Store1; StoreLoad; Load2 確保 Store1 數(shù)據(jù)對其他處理器變得可見(指刷新到內(nèi)存),之前于 Load2 及所有后續(xù)裝載指令的裝載。

          我個人不建議普通人深入JMM限制重排序具體是如何使用內(nèi)存屏障的,因為happens-before的存在就是為了幫助你屏蔽掉具體的內(nèi)存屏障的。

          猜測執(zhí)行

          ?

          了解即可

          ?

          編譯器和處理器會采用「猜測執(zhí)行(Speculation)」 。比如:

          static volatile int a = 1;
          static volatile boolean flag = true;     
          if(flag){
            System.out.println(a * a);
          }

          由于某種原因,比如上述代碼,沒有可能將flag修改為false,因此CPU猜測會執(zhí)行輸出a*a,于是:盡管if 與 {}之間存在依賴關(guān)系,但仍可以提前讀取并計算a*a,然后把計算結(jié)果臨時保存到一個名為「重排序緩沖(Reorder Buffer,ROB)」 的硬件緩存中。像這樣的代碼,是特殊的,但對于程序員是透明的,并不影響我們的程序的執(zhí)行結(jié)果。

          解決原子性:CAS與鎖

          CAS :Compare and Swap

          CAS:Compare and Swap,比較并交換。用于在硬件層面上提供「原子性操作」。在 Intel 處理器中,CAS通過指令cmpxchg實(shí)現(xiàn)。比較是否和給定的數(shù)值一致,如果一致則修改,不一致則不修改。

          CAS有如下三個變量

          • V:要更新的變量(var)
          • E:預(yù)期值(expected) 就是舊值
          • N:新值(new)

          CAS操作:判斷V是否等于E,如果等于,將V的值設(shè)置為N;如果不等,說明已經(jīng)有其它線程更新了V,則當(dāng)前線程放棄更新,什么都不做。

          當(dāng)多個線程同時使用CAS操作一個變量時,只有一個會勝出,并成功更新,其余均會失敗,但失敗的線程并不會被掛起,僅是被告知失敗,并且允許再次嘗試,當(dāng)然也允許失敗的線程放棄操作。

          CAS實(shí)現(xiàn)原理

          CAS憑啥能保證原子性呢?會不會出現(xiàn)兩個線程同時做CAS并發(fā)現(xiàn)Var = Eepected,然后同時修改呢?

          CAS是一條CPU的原子指令,是CPU層面保證的。Linux的X86下主要是通過cmpxchgl這個指令,但在多處理器情況下必須使用lock指令加鎖來完成。不同的操作系統(tǒng)和處理器的實(shí)現(xiàn)各不相同。

          CAS實(shí)現(xiàn)原子操作三大問題

          ABA問題

          A->B->A,CAS檢查不出變化。

          ABA問題的本質(zhì)是:一次CAS的舊值與另一次CAS的新值完全一樣。

          因此我們可以:加上「版本號或者時間戳」

          JUC解決ABA問題:AtomicStampedReference,添加了時間戳

          循環(huán)時間長開銷大

          如果自旋CAS長時間不成功,會占用大量的CPU資源。

          讓JVM支持處理器提供的「pause指令」

          只能保證一個共享變量的原子操作

          如果要保證多個變量的操作的原子性,CAS就無能為力了。我們有兩種解決方案:

          • 鎖機(jī)制,保證臨界區(qū)的代碼只有一個線程可以執(zhí)行
          • 使用JDK 1.5提供的AtomicReference類保證對象的原子性,把多個變量放到一個對象里面進(jìn)行CAS操作;

          鎖機(jī)制

          JVM實(shí)現(xiàn)鎖的方式幾乎都用了循環(huán)CAS,幾乎所有的Java的鎖實(shí)現(xiàn)都依賴于CAS操作。后續(xù)分析synchronized與lock時會體現(xiàn)出如何用CAS來實(shí)現(xiàn)鎖的。

          Java主要提供了關(guān)鍵字synchronized和接口Lock用于實(shí)現(xiàn)鎖機(jī)制。

          三大問題小結(jié)

          到這里JMM就分別解決了并發(fā)編程的三大問題,但還沒有實(shí)現(xiàn)基本的功能:顯式同步,隱式通信。Java提供了若干同步的方式,在本章節(jié)不希望具體到某一種同步方式,而是所有的同步方式。即對于開發(fā)者而言,我們更希望關(guān)注線程間具體如何同步,那么在正確同步的代碼,JMM又提供了什么樣的保證呢?

          順序一致性:線程顯式同步落地

          順序一致性內(nèi)存模型是一個理論參考模型,通常CPU,編程語言的內(nèi)存模型的設(shè)計都會參考順序一致性內(nèi)存模型。

          「JMM保證:如果程序是正確同步的,那么執(zhí)行結(jié)果與該程序在順序一致性內(nèi)存模型中的執(zhí)行結(jié)果相同。」 看的一臉懵?什么是順序一致性模型?下面我們來幫助你理解這句話的含義:

          順序一致性內(nèi)存模型

          順序一致性內(nèi)存模型具體是如下兩條:

          • 一個線程中的所有操作必須按照程序的順序來執(zhí)行
          • (不管程序是否同步)所有線程都只能看到一個單一的操作執(zhí)行順序。在順序一致性內(nèi)存模型中,每個操作都必須「原子執(zhí)行且立刻對所有線程可見」

          可以看到,順序一致性模型保證了:「每個操作的原子性、可見性,以及單線程內(nèi)所有操作的絕對有序性」。也就是說,只要程序正確同步,我們完全可以將多線程的執(zhí)行理解成一個單一的操作執(zhí)行順序,所有操作原子,立即可見,且嚴(yán)格按照順序執(zhí)行。這是一個非常強(qiáng)力的保證。這使得開發(fā)者能夠更加關(guān)注「同步的正確性」,而非總是為了「隱式通信」而擔(dān)憂。

          JMM規(guī)范小結(jié)

          JMM是Java提出的實(shí)現(xiàn)并發(fā)編程的規(guī)范,提供兩個基礎(chǔ)功能:

          • 線程通信
          • 線程同步

          并采用了顯式同步,隱式通信的模型實(shí)現(xiàn)上述功能。

          同時為了克服硬件,操作系統(tǒng)等等帶來的不確定性:

          • 規(guī)定了線程本地內(nèi)存與主存來解決變量的「可見性」
          • 提供happens-before規(guī)則,通過插入內(nèi)存屏障從而限制重排序來解決程序執(zhí)行的「有序性」
          • 通過CAS與鎖機(jī)制(鎖的實(shí)現(xiàn)也依賴于CAS)來解決程序執(zhí)行的「原子性」

          至此,JMM就講解完畢了。過幾天會和大家一起來看看具體Java提供了什么機(jī)制或者關(guān)鍵字來實(shí)現(xiàn)并發(fā)編程的。

          ?

          預(yù)告一下:synchronized輕量級鎖會自旋嗎?不會。下篇文章會具體分析synchronized的原理。如果覺得寫的還不錯就點(diǎn)個贊吧。本人水平有限,如果有疑問或?qū)戝e的地方請在評論區(qū)指正我,感激不盡

          ?

          瀏覽 608
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  特级无码一区二区三区 | 麻豆传剧原创在线观看 | 国产激情综合五月久久 | 五月天国产视频乱码免费 | 熊猫成人免费视频网站 |