[Java多線程 三]---JMM內存模型

轉載自:
http://blog.csdn.net/sinat_33087001/article/details/73607625

高效併發

問題提出:爲什麼要使用高效併發

使用場景一:運行速度的差距

計算機的運行速度與它的存儲和通信子系統速度的差距太大,大量的時間都花費在磁盤I/O、網通通信或者數據的訪問上。造成很大的浪費,最好的辦法就是讓計算機同時處理幾項任務。

使用場景二:服務端對多客戶端

併發另一個場景:服務端同時對多個客戶端提供服務。衡量一個服務性能的高低好壞,每秒事務處理數Transactions Per Scend,TPS,是最重要的指標之一,代表一秒內服務端平均響應的請求總數,TPS的值和併發能力聯繫非常密切。對於計算量相同的任務,程序線程併發協調得有條不紊,效率自然就會越高;反之,程序之間頻繁阻塞甚至死鎖,將會大大降低程序的併發能力。而服務端是Java最擅長的領域之一。

硬件的效率與一致性

解決運算速度衝突

大多數運算中,處理器都要和內存進行交互,如讀取數據、存儲結果等。由於計算機存儲設備和處理器運算速度有幾個數量級的差距,所以現代計算機系統都不得不加入一層讀寫速度儘可能接近處理器運算速度的高緩存Cache來作爲內存與處理器之間的緩衝:將運算需要使用的數據複製到緩存中,讓運算能快速進行,當運算結束後再從緩存中同步回內存,這樣處理器就無須等待緩慢的內存讀寫了。

解決運算衝突卻導致了高速緩存的一致性問題。

解決緩存一致性衝突

高緩存Cache引入了緩存的一致性Cache Coherence的問題。在多處理器系統中,每個處理器都有自己的高速緩存,又共享同一主內存,可能導致各自的緩存數據不一致。需要各處理器訪問緩存時遵循一些協議,在讀寫時根據協議來操作,如MSI,MESI,MOSI,Snapse,Firefly,dRAGON Protocol等。處理器還可能會對輸入的代碼進行亂序執行優化,之後又將亂序結果重組,保證該結果與順序執行結果一致,還有指令重排序優化。

這裏寫圖片描述

內存模型在特定的操作協議下對特定的內存或高速緩存進行讀寫訪問的過程抽象。

Java內存模型(JMM)

JMM,Java Memory Model,用來屏蔽掉各種硬件和操作系統之間的內存訪問差異,以實現讓Java程序在各平臺下都能達到一致的內存訪問效果

在此之前,主流程序語言(如C/C++等)直接使用物理硬件和操作系統的內存模型,因此,會由於不同平臺上內存模型的差異,有可能導致程序在一套平臺上併發完全正常,而在另外一套平臺上併發訪問卻經常出錯,因此在某些場景就必須針對不同的平臺來編寫程序。

JMM主要目標:定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存取出變量這樣的底層細節。

注意:此處變量指實例字段,靜態字段和構成數組對象的元素,但不包括局部變量,因爲其是線程私有的,不會被共享,也就不會有競爭問題。

主內存與工作內存

  • JMM規定所有變量均存儲在主內存(虛擬機內存的一部分)中。每條線程還有自己的工作內存,類比高速緩存。

  • 線程對變量的所有操作都在工作內存中,不能直接在主內存中讀寫操作。

  • 不同線程之間也不能直接訪問對方的工作內存中的變量。只能通過主內存來傳遞變量值。

注意:主內存與工作內存和Java內存區域的堆棧方法區等並不是同一個層次的內存劃分。如果硬要對應的話:

  • 那從變量、主內存、工作內存的定義來看,主內存主要對應於Java堆中的對象實例數據部分 ,而工作內存則對應於虛擬機棧中的部分區域。

  • 主內存直接對應於物理硬件的內存工作內存優先存儲寄存器和高速緩存,因爲程序運行時主要訪問讀寫的是工作內存。

這裏寫圖片描述

內存間交互操作

操作順序

主內存與工作內存之間具體的交互協議:一個變量如何從主內存拷貝到工作內存、如何從工作內存同步回主內存等的細節。

Java內存模型定義了8種操作來完成,虛擬機實現時必須保證下面提及的每一種操作都是原子操作。對於long 和double類型的變量,store,read,write,load操作在某些平臺上允許有例外。

  • lock(鎖定):作用於主內存的變量,它把一個變量標誌爲一條線程獨佔的狀態。

  • unlock(解鎖):作用於主內存中的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。

  • read(讀取):作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用。

  • load(載入):作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。

  • use(使用):作用於工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的字節碼指令時將執行這個操作

  • assign(賦值):作用於工作內存的變量,它把一個從執行引擎接受到的值賦給工作內存的變量,遇到賦值的字節碼時執行

  • store(存儲):作用於工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便隨後的write操作使用。

  • write(寫入):作用於主內存中的變量,它把store操作從主內存中得到的變量值放入主內存的變量中

這裏寫圖片描述

以上操作,僅保持順序執行即可,不用保證連續執行。如 read a read b load b load a。

操作衝突

變量操作相關的========================================================
  • 不允許read和load、store和write操作之一單獨出現,即不允許一個變量從主內存讀取了但工作內存不接受,或者從工作內存發起回寫了但主內存不接受的情況

  • 不允許一個線程丟棄它的最近assign操作,即變量在工作內存中改變了之後必須把該變化同步回主內存。

  • 不允許一個線程無原因的(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。即不能對變量沒做任何操作卻無原因的同步回主內存

  • 一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量,就是對一個變量執行use和store之前必須先執行過了load和assign操作。

lock操作相關的========================================================
  • 個變量在同一個時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重複執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量纔會被解鎖。

  • 如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。

  • 如果一個變量事先沒有被lock操作鎖定,則不允許對它執行unlock操作,也不允許去unlock一個被其他線程鎖定住的變量。

  • 對一個變量執行unlock操作之前,必須先把此變量同步回主內存中(執行store write)。
    以上可以完全確定Java程序中哪些內存訪問操作在併發下是安全的。

volatile型變量

volatile,Java虛擬機提供的最輕量級的同步機制。

可見性

volatile第一可以保證變量對所有線程的可見性,即一條線程修改了變量的值,新值對於其他線程來說是可以立即得知的。volatile變量在各個線程的工作內存中不存在一致性問題(即時存在,由於每次使用之前都得刷新,執行引擎看不到不一致的情況,所以認爲是 不存在一致性問題)。

如果運算操作不是原子操作,導致volatile變量的運算在併發下一樣是不安全的。依然沒法保證volatile同步的正確性。由於volatile變量只能保證可見性,在不符合以下兩條規則的運算場景中,仍需要加鎖synchronized或java.util.concurrent中的原子類來保證原子性(如果把一個事務可看作是一個程序,它要麼完整的被執行,要麼完全不執行。這種特性就叫原子性*):

  • 對變量的寫入操作不依賴於該變量的當前值(比如a=0;a=a+1的操作,整個流程爲a初始化爲0,將a的值在0的基礎之上加1,然後賦值給a本身,很明顯依賴了當前值),或者確保只有單一線程修改變量。

  • 該變量不會與其他狀態變量納入不變性條件中。(當變量本身是不可變時,volatile能保證安全訪問,比如雙重判斷的單例模式。但一旦其他狀態變量參雜進來的時候,併發情況就無法預知,正確性也無法保障)。

/**
 * 基於雙重判斷的單例模式
 */
public class Singleton {

    private volatile static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
            Singleton.getInstance();
    }
}
//volatile修飾的變量在賦值後會多執行一個lock addl..的操作,相當於一個內存屏障(Memory Barrier/Memory Fence ,指重排序時不能把後面的指令重排序到內存屏障之前的位置,也意味着在該操作時,所有之前的指令操作都已經執行完畢),只有一個CPU訪問內存時並不需要,但如果有多個CPU訪問同一內存,且其中一個在觀測另一個,就需要內存屏障來保證一致性。

內存屏障

volatile還有個特性就是,可以禁止指令進行重排序優化。普通變量僅僅會保證在該方法的執行過程中所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致。如下例子:

Map configOptions;
char [] configText;

//此變量必須定義爲volatile
volatile boolean initialized = false;

//假設一下代碼在線程A中執行,模擬讀取配置信息,當讀取完成後,將initialized設置爲true來通知其他線程配置可使用
configOptions = new HashMap();
configText = readConfigFile(fileName);
processConfigOptions(configText, configOptions);
initialized = true;

//假設以下代碼在線程B中執行,等待initialized 爲true,代表線程A已經把配置信息初始化完成
while(!initialized ){
    sleep();
}
//使用線程A中初始化好的配置信息
doSomethingWithConfig();

如果initialized 沒有使用volatile修飾,就可能由於指令重排的優化,導致位於線程A中的最後一句代碼“initialized = true”被提前執行,這樣在線程B中使用配置信息的代碼就可能出現錯誤。

性能評價

某些情況下volatile的同步機制比鎖要好,但很難量化這種優勢。volatile自己和自己比較,它的讀操作的性能消耗和普通變量幾乎沒啥區別,但寫操作要慢一些,因爲需要在本地代碼中插入許多內存屏障指令來保證處理器不發生亂序執行。即便如此,在大多數場景下,volatile總開銷仍然比鎖要低,根據volatile語義是否滿足場景來選擇。如果情況不合適,就使用傳統的synchronized關鍵字同步共享變量的訪問,用來保證程序正確性(這個關鍵字的性能會隨着jvm不斷完善而不斷提升,將來性能會慢慢逼近volatile)。

JMM對volatile的特殊規則

volatile變量V,W,線程T,進行read load use assign store write操作:

  • 對於T來說,必須保證對V的load和use連續一起出現,即在工作內存中,每次使用V前都必須先從主內存中刷新最新值,用於保證能看到其他線程對V的修改後的值

  • T對V的assign和store必須連續一起出現。即在工作內存中,每次修改V後都必須立刻同步回主內存中,保證其他線程看到自己對V 的修改。

  • 假定A是T對V的use assign,F是A相關聯的load或store;P是和F相應的對變量V的read或write;同樣,B是T對W的use或assign,G是B相關聯的load或store,Q是G相應的對變量W的read或write。如果A先於B,那麼P先於Q(volatile修飾的變量不會被指令重排序優化,保證代碼的執行順序與程序順序相同)。

對於long和double變量的特殊規則

對於64位的數據類型(long和double),在模型中特別定義了一條寬鬆的規定:允許虛擬機將沒有被volatile修飾的64位數據的讀寫劃分爲兩次32位的操作來進行,即允許虛擬機不保證64位數據類型的load、store、read和write這四個操作的原子性。虛擬機幾乎都選擇將64位數據的讀寫操作作爲原子操作來對待,因此在編寫代碼時一般不需要用到將long和double變量專門聲明爲volatile

原子性、可見性與有序性

原子性(Atomicity)

Java內存模型來直接保證的原子性變量操作包括read、load、use、assign、store和write這六個,我們可以大致的認爲基本數據類型的訪問讀寫是具備原子性的(long和double除外)。Java內存模型還提供了lock和unlock操作來滿足這種需求,儘管虛擬機未把lock和unlock操作直接開放給用戶使用,但是卻提供了更高層次的字節碼指令monitorenter和monitorexit來隱式地使用這兩個操作,這兩個字節碼指令反映到Java代碼中就是同步塊——synchronized關鍵字,因此在synchronized塊之間的操作也具備原子性。

可見性(Visibility)

可見性是指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。Java內存模型是通過在變量修改後將新值同步回主內存,在變量讀取前從主內存刷新變量值這種依賴主內存作爲傳遞媒介的方式來實現可見性的,無論是普通變量還是volatile變量都是如此,普通變量與volatile變量的區別是,volatile的特殊規則保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新。因此,可以說volatile保證了多線程操作時變量的可見性,而普通變量則不能保證這一點。

除了volatile之外,Java還有兩個關鍵字能實現可見性,即synchronized和final。同步塊的可見性是由“對一個變量執行unlock操作之前,必須先把此變量同步回主內存中(執行store、write操作)”這條規則獲得的,而final關鍵字的可見性是指:被final修飾的字段在構造器中一旦初始化完成,並且構造器沒有把“this”的引用傳遞出去(this引用逃逸是一件很危險的事情,其他線程有可能通過這個引用訪問到“初始化了半”的對象),那在其他線程中就能看見final字段的值。

有序性(Ordering)

Java程序的天然有序性:在本線程內觀察,所有操作都是有序的;在一個線程中觀察另外一個線程,所有操作都是無序的。(前半句是指線程內表現爲串行的語義,後半句是指令重排序現象和工作內存與主內存同步延遲現象)。用synchronized和volatile關鍵字來保證線程操作之間的有序性。volatile本省就包含禁止指令重排序的語義,而synchronized則是因爲:一個變量在同一時刻只允許一條線程對齊進行lock操作。這個規則決定了持有同一個鎖的兩個同步塊只能串行的進入。

先行發生原則

先行發生是Java內存模型中定義的兩項操作之間的偏序關係
如果Java內存模型中所有的有序性都只靠volatile和synchronized來完成,那麼有一些操作將會變得很繁瑣。java內存模型中的一個重點原則——先行發生原則(Happens-Before),使用這個原則作爲依據,來指導你判斷是否存在線程安全和競爭問題。 以下先行發生關係無須任何同步器協助就已經存在,可以在編碼中直接使用。如果兩個操作之間的關係不在此列,並且無法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機可以對它們隨意地進行重排序。

八個原則

關於順序執行的三個原則

  • 程序次序規則(Program Order Rule):在一個線程內,按照程序代碼順序,書寫在前面的操作先行發生於書寫在後面的操作。準確地說,應該是控制流順序而不是程序代碼順序,因爲要考慮分支、循環等結構。

  • 傳遞性(Transitivity):如果操作A先行發生於操作B,操作B先行發生於操作C,那就可以得出操作A先行發生於操作C的結論。

    • 對象終結規則(Finalizer Rule)一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize()方法的開始

關於鎖定與讀寫的兩個原則

  • 管程鎖定規則(Monitor Lock Rule)一個unlock操作先行發生於後面對同一個鎖的lock操作。這裏必須強調的是同一個鎖,而“後面”是指時間上的先後順序。

  • volatile變量規則(Volatile Variable Rule)對一個volatile變量的寫操作先行發生於後面對這個變量的讀操作,這裏的“後面”同樣是指時間上的先後順序。

關於線程的三個原則

  • 線程啓動規則(Thread Start Rule):Thread對象的start()方法先行發生於此線程的每一個動作

  • 線程終止規則(Thread Termination Rule):線程中的所有操作都先行發生於對此線程的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行

  • 線程中斷規則(Thread Interruption Rule)對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷髮生。

注意

一個操作“時間上的先發生”不代表這個操作會是“先行發生”

private int value = 0;
public void setValue(int value){
    this.value = value;
}
public int getValue(){
    return value;
}

假設存在線程A和B,線程A先調用setValue(1),然後線程B調用了同一個對象getValue(),那麼線程B返回的值是什麼?分析:兩個線程調用,不在一個線程中,所以程序次序規則不適用;沒有同步塊—管程鎖定規則不適用;value沒有被volatile修飾,所以volatile變量規則不適用;後面的線程啓動、終止、中斷規則和對象終結規則也扯不上關係,所以儘管在時間上A先於B,但無法確定B中getValue()返回值結果,因此我們說這裏的操作時線程不安全的。

如何修復呢?可以爲set、get方法定義爲synchronized方法,這樣可以使用管程鎖定規則;或者把value設定爲volatile變量,由於set方法對value的修改不依賴value的原值,滿足volatile關鍵字使用場景。

那如果一個操作“先行發生”是否就能推導出這個操作必定是“時間上的先發生”呢?很遺憾,這個推論也是不成立的,一個典型的例子就是多次提到的“指令重排序”。所以一個操作“先行發生”不代表這個操作會在時間上先發生

時間先後順序與先行發生原則之間基本沒有太大的關係

Java與線程

併發並不一定依賴多線程,但Java裏談論併發大多數都與線程脫不開關係。主流的操作系統都提供了線程實現,Java語言則提供了在不同硬件和操作系統平臺下對線程操作的統一處理。

線程的實現

實現線程主要有3種方式:使用內核線程實現使用用戶線程實現和使用用戶線程加輕量級進程混合實現

使用內核線程實現

內核線程(Kernel-Level Thread,KLT)就是直接由操作系統內核(Kernel,下稱內核)支持的線程,這種線程由內核來完成線程切換,內核通過操縱調度器(Scheduler)對線程進行調度,並負責將線程的任務映射到各個處理器上。每個內核線程可以視爲內核的一個分身,這樣操作系統就有能力同時處理多件事情,支持多線程的內核就叫做多線程內核(Multi-Threads Kernel)。

程序一般不會直接去使用內核線程,而是去使用內核線程的一種高級接口——輕量級進程(Light Weight Process,LWP),輕量級進程就是我們通常意義上所講的線程,由於每個輕量級進程都由一個內核線程支持,因此只有先支持內核線程,纔能有輕量級進程。這種輕量級進程與內核線程之間1:1的關係稱爲一對一的線程模型

這裏寫圖片描述

優點

由於內核線程的支持,每個輕量級進程都成爲一個獨立的調度單元,即使有一個輕量級進程在系統調用中阻塞了,也不會影響整個進程繼續工作。

缺點

  • 首先,由於是基於內核線程實現的,所以各種線程操作,如創建、析構及同步,都需要進行系統調用。而系統調用的代價相對較高,需要在用戶態(User Mode)和內核態(Kernel Mode)中來回切換.

  • 其次,每個輕量級進程都需要有一個內核線程的支持,因此輕量級進程要消耗一定的內核資源(如內核線程的棧空間),因此一個系統支持輕量級進程的數量是有限的。

使用用戶線程實現

用戶線程指的是完全建立在用戶空間的線程庫上,系統內核不能感知線程存在的實現。用戶線程的建立、同步、銷燬和調度完全在用戶態中完成,不需要內核的幫助。如果程序實現得當,這種線程不需要切換到內核態,因此操作可以是非常快速且低消耗的,也可以支持規模更大的線程數量,部分高性能數據庫中的多線程就是由用戶線程實現的。這種進程與用戶線程之間1:N的關係稱爲一對多的線程模型

這裏寫圖片描述

優點

使用用戶線程的優勢在於不需要系統內核支援

缺點

劣勢也在於沒有系統內核的支援,所有的線程操作都需要用戶程序自己處理。線程的創建、切換和調度都是需要考慮的問題,而且由於操作系統只把處理器資源分配到進程,那諸如“阻塞如何處理”、“多處理器系統中如何將線程映射到其他處理器上”這類問題解決起來將會異常困難

Java、Ruby等語言都曾經使用過用戶線程,最終又都放棄使用它。

使用用戶線程加輕量級進程混合實現

在這種混合實現下,既存在用戶線程,也存在輕量級進程。用戶線程還是完全建立在用戶空間中,因此用戶線程的創建、切換、析構等操作依然廉價,並且可以支持大規模的用戶線程併發。而操作系統提供支持的輕量級進程則作爲用戶線程和內核線程之間的橋樑,這樣可以使用內核提供的線程調度功能及處理器映射,並且用戶線程的系統調用要通過輕量級線程來完成,大大降低了整個進程被完全阻塞的風險。

這裏寫圖片描述

Java線程的實現

線程模型替換爲基於操作系統原生線程模型來實現。因此,在目前的JDK版本中,操作系統支持怎樣的線程模型,在很大程度上決定了Java虛擬機的線程是怎樣映射的,這點在不同的平臺上沒有辦法達成一致,虛擬機規範中也並未限定Java線程需要使用哪種線程模型來實現。

對於Sun JDK來說,它的Windows版與Linux版都是使用一對一的線程模型實現的一條Java線程就映射到一條輕量級進程之中,因爲Windows和Linux系統提供的線程模型就是一對一的。

在Solaris平臺中,由於操作系統的線程特性可以同時支持一對一及多對多

Java線程調度

線程調度是指系統爲線程分配處理器使用權的過程,主要調度方式有兩種,分別是協同式線程調度(Cooperative Threads-Scheduling)和搶佔式線程調度(Preemptive Threads-Scheduling)。

協同式線程調度

如果使用協同式調度的多線程系統,線程的執行時間由線程本身來控制,線程把自己的工作執行完了之後,要主動通知系統切換到另外一個線程上。

優點

協同式多線程的最大好處是實現簡單,而且由於線程要把自己的事情幹完後纔會進行線程切換,切換操作對線程自己是可知的,所以沒有什麼線程同步的問題

缺點

它的壞處也很明顯:線程執行時間不可控制,甚至如果一個線程編寫有問題,一直不告知系統進行線程切換,那麼程序就會一直阻塞在那裏

搶佔式線程調度

如果使用搶佔式調度的多線程系統,那麼每個線程將由系統來分配執行時間,線程的切換不由線程本身來決定(在Java中,Thread.yield()可以讓出執行時間,但是要獲取執行時間的話,線程本身是沒有什麼辦法的)。

優先級

雖然Java線程調度是系統自動完成的,但是我們還是可以“建議”系統給某些線程多分配一點執行時間,另外的一些線程則可以少分配一點——這項操作可以通過設置線程優先級來完成。Java語言一共設置了10個級別的線程優先級(Thread.MIN_PRIORITY至
Thread.MAX_PRIORITY),在兩個線程同時處於Ready狀態時,優先級越高的線程越容易被系統選擇執行。

比Java線程優先級少的系統,就不得不出現幾個優先級相同的情況了

這裏寫圖片描述

狀態轉換

Java定義了5種線程狀態,在任意一個點一個線程只能有且只有其中一種狀態。無限等待和等待可以算在一起。所以共五種。

新建(New)

創建後尚未啓動的線程。

運行(Runnable)

Runnable包括操作系統線程狀態中的Running和Ready,也就是處於此狀態的線程有可能正在執行,也有可能等待CPU爲它分配執行時間。線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於“可運行線程池”中,變得可運行,只等待獲取CPU的使用權。即在就緒狀態的進程除CPU之外,其它的運行所需資源都已全部獲得。

無限期等待(Waiting)

該狀態下線程不會被分配CPU執行時間,要等待被其他線程顯式喚醒

  • 沒有設置Timeout參數的Object.wait()方法。

  • 沒有設置Timeout參數的Thread.join()方法。

  • LockSupport.park()方法。

限期等待(Timed Waiting)

處於這種狀態的線程也不會被分配CPU執行時間,不過無須等待被其他線程顯式地喚醒,在一定時間之後它們會由系統自動喚醒。以下方法會讓線程進入限期等待狀態:

  • Thread.sleep()方法。

  • 設置了Timeout參數的Object.wait()方法。

  • 設置了Timeout參數的Thread.join()方法。

  • LockSupport.parkNanos()方法。

  • LockSupport.parkUntil()方法。

阻塞(Blocked)

線程被阻塞了。與等待狀態的區別是:阻塞在等待着獲取到一個排他鎖,這個事件將在另外一個線程放棄這個鎖的時候發生;而“等待狀態”則是在等待一段時間,或者喚醒動作的發生。在程序等待進入同步區域的時候,線程將進入這種狀態。

阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。
阻塞的情況分三種

  • 等待阻塞:運行的線程執行wait()方法,該線程會釋放佔用的所有資源,JVM會把該線程放入“等待池”中。進入這個狀態後,是不能自動喚醒的,必須依靠其他線程調用notify()或notifyAll()方法才能被喚醒,,即無限期等待
  • 同步阻塞運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入“鎖池”中。
  • 其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。即限期等待。

結束(Terminated)

已終止線程的線程狀態,線程已經結束執行。

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章