亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java線程學習之并發編程知識點有哪些

發布時間:2022-06-22 13:35:10 來源:億速云 閱讀:123 作者:iii 欄目:編程語言

本文小編為大家詳細介紹“Java線程學習之并發編程知識點有哪些”,內容詳細,步驟清晰,細節處理妥當,希望這篇“Java線程學習之并發編程知識點有哪些”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。

Java線程學習之并發編程知識點有哪些

一、JMM 基礎-計算機原理

Java 內存模型即 Java Memory Model,簡稱JMM。JMM 定義了Java 虛擬機 (JVM)在計算機內存(RAM)中的工作方式。JVM 是整個計算機虛擬模型,所以 JMM 是隸屬于 JVM 的。Java1.5 版本對其進行了重構,現在的 Java 仍沿用了 Java1.5 的版本。Jmm 遇到的問題與現代計算機中遇到的問題是差不多的。
        物理計算機中的并發問題,物理機遇到的并發問題與虛擬機中的情況有不少 相似之處,物理機對并發的處理方案對于虛擬機的實現也有相當大的參考意義。
       根據《Jeff Dean 在 Google 全體工程大會的報告》我們可以看到

Java線程學習之并發編程知識點有哪些

計算機在做一些我們平時的基本操作時,需要的響應時間是不一樣的。

以下案例僅做說明,并不代表真實情況。

如果從內存中讀取 1M 的 int 型數據由 CPU 進行累加,耗時要多久?
       做個簡單的計算,1M 的數據,Java 里 int 型為 32 位,4 個字節,共有 1024*1024/4 = 262144 個整數 ,則 CPU 計算耗時:262144 0.6 = 157286 納秒, 而我們知道從內存讀取 1M 數據需要 250000 納秒,兩者雖然有差距(當然這個差距并不小,十萬納秒的時間足夠 CPU 執行將近二十萬條指令了),但是還在 一個數量級上。但是,沒有任何緩存機制的情況下,意味著每個數都需要從內存 中讀取,這樣加上 CPU 讀取一次內存需要 100 納秒,262144 個整數從內存讀取 到 CPU 加上計算時間一共需要 262144100+250000 = 26 464 400 納秒,這就存在 著數量級上的差異了。

而且現實情況中絕大多數的運算任務都不可能只靠處理器“計算”就能完成,處理器至少要與內存交互,如讀取運算數據、存儲運算結果等,這個 I/O 操作是基本上是無法消除的(無法僅靠寄存器來完成所有運算任務)。早期計算機中 cpu 和內存的速度是差不多的,但在現代計算機中,cpu 的指令速度遠超內存的存取速度,由于計算機的存儲設備與處理器的運算速度有幾個數量級的差距,所 以現代計算機系統都不得不加入一層讀寫速度盡可能接近處理器運算速度的高速緩存(Cache)來作為內存與處理器之間的緩沖:將運算需要使用到的數據復制到緩存中,讓運算能快速進行,當運算結束后再從緩存同步回內存之中,這樣 處理器就無須等待緩慢的內存讀寫了。

Java線程學習之并發編程知識點有哪些

Java線程學習之并發編程知識點有哪些

在計算機系統中,寄存器是 L0 級緩存,接著依次是 L1,L2,L3(接下來是內存,本地磁盤,遠程存儲)。越往上的緩存存儲空間越小,速度越快,成本也更高;越往下的存儲空間越大,速度更慢,成本也更低。從上至下,每一層都可以看做是更下一層的緩存,即:L0 寄存器是 L1 一級緩存的緩存,L1 是 L2 的緩存,依次類推;每一層的數據都是來至它的下一層,所以每一層的數據是下一 層的數據的子集。

Java線程學習之并發編程知識點有哪些

在現代 CPU 上,一般來說 L0, L1,L2,L3 都集成在 CPU 內部,而 L1 還分 為一級數據緩存(Data Cache,D-Cache,L1d)和一級指令緩存(Instruction Cache, I-Cache,L1i),分別用于存放數據和執行數據的指令解碼。每個核心擁有獨立 的運算處理單元、控制器、寄存器、L1、L2 緩存,然后一個 CPU 的多個核心共 享最后一層 CPU 緩存 L3。

二、Java 內存模型(JMM)

從抽象的角度來看,JMM 定義了線程和主內存之間的抽象關系:線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是 JMM 的一個抽象概念,并不真實存在。它涵蓋了緩存、寫緩沖區、寄存器以及其他的硬件和編譯器優化。

Java線程學習之并發編程知識點有哪些

Java線程學習之并發編程知識點有哪些

2.1、可見性

可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值, 其他線程能夠立即看得到修改的值。
       由于線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存中的變量,那么對于共享變量 V,它們首先是在自己的工作內存,之后再同步到主內存。可是并不會及時的刷到主存中,而是會有一定時間差。很明顯,這個時候線程 A 對變量 V 的操作對于線程 B 而言就不具備可見性了 。
       要解決共享對象可見性這個問題,我們可以使用 volatile 關鍵字或者是加鎖。

2.2、原子性

原子性:即一個操作或者多個操作,要么全部執行并且執行的過程不會被任何因素打斷,要么就都不執行。
       我們都知道 CPU 資源的分配都是以線程為單位的,并且是分時調用,操作系統允許某個進程執行一小段時間,例如 50 毫秒,過了 50 毫秒操作系統就會重新選擇一個進程來執行(我們稱為“任務切換”),這個 50 毫秒稱為“時間片”。 而任務的切換大多數是在時間片段結束以后,。
       那么線程切換為什么會帶來 bug 呢?因為操作系統做任務切換,可以發生在任何一條 CPU 指令執行完!注意,是 CPU 指令,CPU 指令,CPU 指令,而不是高級語言里的一條語句。比如 count++,在 java 里就是一句話,但高級語言里一條語句往往需要多條 CPU 指令完成。其實 count++至少包含了三個 CPU 指令!

三、volatile 詳解

3.1、volatile 特性

可以把對 volatile 變量的單個讀/寫,看成是使用同一個鎖對這些單個讀/寫 操作做了同步

public class Volati {


    //    使用volatile 聲明一個64位的long型變量
    volatile long i = 0L;//    單個volatile 變量的讀
    public long getI() {
        return i;
    }//    單個volatile 變量的寫
    public void setI(long i) {
        this.i = i;
    }//    復合(多個)volatile 變量的 讀/寫
    public void iCount(){
        i ++;
    }}

可以看成是下面的代碼:

public class VolaLikeSyn {

    //    使用 long 型變量
    long i = 0L;
    public synchronized long getI() {
        return i;
    }//     對單個的普通變量的讀用同一個鎖同步
    public synchronized void setI(long i) {
        this.i = i;
    }//    普通方法調用
    public void iCount(){
        long temp = getI();   // 調用已同步的讀方法
        temp = temp + 1L;     // 普通寫操作
        setI(temp);           // 調用已同步的寫方法
    }}

所以 volatile 變量自身具有下列特性:

  • 可見性:對一個 volatile 變量的讀,總是能看到(任意線程)對這個 volatile 變量最后的寫入。

  • 原子性:對任意單個 volatile 變量的讀/寫具有原子性,但類似于 volatile++ 這種復合操作不具有原子性。

volatile 雖然能保證執行完及時把變量刷到主內存中,但對于 count++這種非原子性、多指令的情況,由于線程切換,線程 A 剛把 count=0 加載到工作內存, 線程 B 就可以開始工作了,這樣就會導致線程 A 和 B 執行完的結果都是 1,都寫到主內存中,主內存的值還是 1 不是 2

3.2、volatile 的實現原理

  • volatile 關鍵字修飾的變量會存在一個“lock:”的前綴。

  • Lock 前綴,Lock 不是一種內存屏障,但是它能完成類似內存屏障的功能。Lock 會對 CPU 總線和高速緩存加鎖,可以理解為 CPU 指令級的一種鎖。

  • 同時該指令會將當前處理器緩存行的數據直接寫會到系統內存中,且這個寫 回內存的操作會使在其他 CPU 里緩存了該地址的數據無效。

四、synchronized 的實現原理

Synchronized 在 JVM 里的實現都是基于進入和退出 Monitor 對象來實現方法同步和代碼塊同步,雖然具體實現細節不一樣,但是都可以通過成對的 MonitorEnter 和 MonitorExit 指令來實現。
       對同步塊,MonitorEnter 指令插入在同步代碼塊的開始位置,而 monitorExit 指令則插入在方法結束處和異常處,JVM 保證每個 MonitorEnter 必須有對應的 MonitorExit。總的來說,當代碼執行到該指令時,將會嘗試獲取該對象 Monitor 的所有權,即嘗試獲得該對象的鎖:

  1. 如果 monitor 的進入數為 0,則該線程進入 monitor,然后將進入數設置為 1,該線程即為 monitor 的所有者。

  2. 如果線程已經占有該 monitor,只是重新進入,則進入 monitor 的進入數加 1。

  3. 如果其他線程已經占用了 monitor,則該線程進入阻塞狀態,直到 monitor 的進入數為 0,再重新嘗試獲取 monitor 的所有權。 對同步方法,從同步方法反編譯的結果來看,方法的同步并沒有通過指令 monitorenter 和 monitorexit 來實現,相對于普通方法,其常量池中多了 ACC_SYNCHRONIZED 標示符。
            JVM 就是根據該標示符來實現方法的同步的:當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設置,如果設置了,執行線程將先獲取 monitor,獲取成功之后才能執行方法體,方法執行完后再釋放 monitor。在方法執行期間,其他任何線程都無法再獲得同一個 monitor 對象。

synchronized 使用的鎖是存放在 Java 對象頭里面,Java 對象的對象頭由 mark word 和 klass pointer 兩部分組成:

  1. mark word 存儲了同步狀態、標識、hashcode、GC 狀態等等。

  2. klass pointer 存儲對象的類型指針,該指針指向它的類元數據 另外對于數組而言還會有一份記錄數組長度的數據。

Java線程學習之并發編程知識點有哪些

鎖信息則是存在于對象的 mark word 中,MarkWord 里默認數據是存儲對象的 HashCode 等信息。

Java線程學習之并發編程知識點有哪些

但是會隨著對象的運行改變而發生變化,不同的鎖狀態對應著不同的記錄存儲方式

Java線程學習之并發編程知識點有哪些

4.1、鎖的狀態

對照上面的圖中,我們發現鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態, 它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,目的是為了提高獲得鎖和 釋放鎖的效率。

4.2、偏向鎖

引入背景:大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖,減少不必要的 CAS 操作。
       偏向鎖,顧名思義,它會偏向于第一個訪問鎖的線程,如果在運行過程中, 同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,減少加鎖/解鎖的一些 CAS 操作(比如等待隊列的一些 CAS 操作),這種情況下,就會給線程加一個偏向鎖。 如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM 會消除它身上的偏向鎖,將鎖恢復到標 準的輕量級鎖。它通過消除資源無競爭情況下的同步原語,進一步提高了程序的 運行性能。

看下面圖,了解偏向鎖獲取過程:

Java線程學習之并發編程知識點有哪些

步驟 1、 訪問 Mark Word 中偏向鎖的標識是否設置成 1,鎖標志位是否為 01,確認為可偏向狀態。
       步驟 2、 如果為可偏向狀態,則測試線程 ID 是否指向當前線程,如果是, 進入步驟 5,否則進入步驟 3。
       步驟 3、 如果線程 ID 并未指向當前線程,則通過 CAS 操作競爭鎖。如果競 爭成功,則將 Mark Word 中線程 ID 設置為當前線程 ID,然后執行 5;如果競爭 失敗,執行 4。
       步驟 4、 如果 CAS 獲取偏向鎖失敗,則表示有競爭。當到達全局安全點 (safepoint)時獲得偏向鎖的線程被掛起,偏向鎖升級為輕量級鎖,然后被阻塞在安全點的線程繼續往下執行同步代碼。(撤銷偏向鎖的時候會導致 stop the word)
       步驟 5、 執行同步代碼。

偏向鎖的釋放:

偏向鎖的撤銷在上述第四步驟中有提到。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放偏向鎖,線程不會主動去釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處于被鎖定狀態,撤銷偏向鎖后恢復到未鎖定(標志位為“01”)或輕量級鎖(標志位為“00”)的狀態。

偏向鎖的適用場景:

始終只有一個線程在執行同步塊,在它沒有執行完釋放鎖之前,沒有其它線程去執行同步塊,在鎖無競爭的情況下使用,一旦有了競爭就升級為輕量級鎖,升級為輕量級鎖的時候需要撤銷偏向鎖,撤銷偏向鎖的時候會導致 stop the word 操作;
       在有鎖的競爭時,偏向鎖會多做很多額外操作,尤其是撤銷偏向鎖的時候會導致進入安全點,安全點會導致 stw,導致性能下降,這種情況下應當禁用。

jvm 開啟/關閉偏向鎖
開啟偏向鎖:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 關閉偏向鎖:-XX:-UseBiasedLocking

4.3、 輕量級鎖

輕量級鎖是由偏向鎖升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖;

輕量級鎖的加鎖過程:

  1. 在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態且不允許進行偏向(鎖標志位為“01”狀態,是否為偏向鎖為“0”),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的 Mark Word 的拷貝,官方稱之為 Displaced Mark Word。

  2. 拷貝對象頭中的 Mark Word 復制到鎖記錄中。

  3. 拷貝成功后,虛擬機將使用 CAS 操作嘗試將對象的 Mark Word 更新為指向 Lock Record 的指針,并將 Lock record 里的 owner 指針指向 object mark word。如果更新成功,則執行步驟 4,否則執行步驟 5。

  4. 如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,并且對象 Mark Word 的鎖標志位設置為“00”,即表示此對象處于輕量級鎖定狀態

  5. 如果這個更新操作失敗了,虛擬機首先會檢查對象的 Mark Word 是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,那么它就會自旋等待鎖,一定次數后仍未獲得鎖對象。重量級線程指針指向競爭線程,競爭線程也會阻塞,等待輕量級線程釋放鎖后喚醒他。鎖標志的狀態值變為“10”,Mark Word 中存儲 的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進入阻塞狀態。

4.3.1、自旋鎖原理

自旋鎖原理非常簡單,如果持有鎖的線程能在很短時間內釋放鎖資源,那么那些等待競爭鎖的線程就不需要做內核態和用戶態之間的切換進入阻塞掛起狀態,它們只需要等一等(自旋),等持有鎖的線程釋放鎖后即可立即獲取鎖,這樣就避免用戶線程和內核的切換的消耗。
       但是線程自旋是需要消耗 CPU 的,說白了就是讓 CPU 在做無用功,線程不能一直占用 CPU 自旋做無用功,所以需要設定一個自旋等待的最大時間。
       如果持有鎖的線程執行的時間超過自旋等待的最大時間扔沒有釋放鎖,就會導致其它爭用鎖的線程在最大等待時間內還是獲取不到鎖,這時爭用線程會停止自旋進入阻塞狀態。

4.3.2、自旋鎖的優缺點

自旋鎖盡可能的減少線程的阻塞,這對于鎖的競爭不激烈,且占用鎖時間非常短的代碼塊來說性能能大幅度的提升,因為自旋的消耗會小于線程阻塞掛起操作的消耗。
       但是如果鎖的競爭激烈,或者持有鎖的線程需要長時間占用鎖執行同步塊,這時候就不適合使用自旋鎖了,因為自旋鎖在獲取鎖前一直都是占用 cpu 做無用 功,占著 茅坑 不 那啥,線程自旋的消耗大于線程阻塞掛起操作的消耗,其它需要 cup 的線程又不能獲取到 cpu,造成 cpu 的浪費。

4.3.3、自旋鎖時間閾值

自旋鎖的目的是為了占著 CPU 的資源不釋放,等到獲取到鎖立即進行處理。 但是如何去選擇自旋的執行時間呢?如果自旋執行時間太長,會有大量的線程處于自旋狀態占用 CPU 資源,進而會影響整體系統的性能。因此自旋次數很重要。
       JVM 對于自旋次數的選擇,jdk1.5 默認為 10 次,在 1.6 引入了適應性自旋鎖, 適應性自旋鎖意味著自旋的時間不在是固定的了,而是由前一次在同一個鎖上的 自旋時間以及鎖的擁有者的狀態來決定,基本認為一個線程上下文切換的時間是 最佳的一個時間。

JDK1.6 中-XX:+UseSpinning 開啟自旋鎖; JDK1.7 后,去掉此參數,由 jvm 控 制;

Java線程學習之并發編程知識點有哪些

4.3.4、不同鎖的比較

Java線程學習之并發編程知識點有哪些

讀到這里,這篇“Java線程學習之并發編程知識點有哪些”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

鹿邑县| 四川省| 龙川县| 吴江市| 沙田区| 侯马市| 嘉祥县| 茌平县| 澄迈县| 永顺县| 马尔康县| 乡城县| 通江县| 开封市| 格尔木市| 南阳市| 康马县| 巍山| 广宗县| 新宁县| 霍城县| 大洼县| 来安县| 武夷山市| 沈丘县| 泗水县| 铜山县| 绥棱县| 长春市| 葫芦岛市| 多伦县| 永德县| 梁山县| 天津市| 玉溪市| 南阳市| 乌什县| 霍山县| 青河县| 扶余县| 碌曲县|