深刻理解Java併發編程之線程Thread

前言

現代操做系統在運行一個程序時,會爲其建立一個進程。例如,啓動一個Java程序,操做系統就會建立一個Java進程。現代操做系統調度的最小單元是線程,也叫輕量級進程(Light Weight Process),在一個進程裏能夠建立多個線程,這些線程都擁有各自的計數器、堆棧和局部變量等屬性,而且可以訪問共享的內存變量。處理器在這些線程上高速切換,讓使用者感受到這些線程在同時執行。html

Java線程經過調用線程的start()方法進行啓動,隨着run()方法的執行完畢,線程也隨之終止。java

線程優先級

  1. Java線程優先級從低到高爲1~10,默認優先級爲5。以下爲Thread.java的源碼:
/**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;
複製代碼
  1. 線程優先級不能做爲程序正確性的依賴,由於操做系統能夠徹底不用理會Java線程對於優先級的設定。

線程的狀態

Java線程在運行的生命週期中可能處於6種不一樣的狀態,在給定的一個時刻,線程只能處於其中的一個狀態。以下內容截取JDK 1.8 Thread.java的源碼:編程

  1. NEW: 初始轉態,線程被構建,可是尚未調用start()方法。
  2. RUNNABLE: 正在執行的線程狀態,JVM中runnable線程狀態對應於操做系統中的就緒和運行兩種狀態。
  3. BLOCKED: 線程等待monitor互斥量的阻塞狀態,在blocked狀態的線程正在被執行Object.wait()後等着進入或者再次同步塊或者同步方法。
  4. WAITING: 等待狀態,下列方法會致使線程處於等待狀態:
    • Object.wait with no timeout
    • Thread.join with on timeout
    • LockSupport.park
  5. TIMED_WAITING: 超時等待,超過等待時間便會自動返回運行狀態,下列方法會致使線程處於超時等待狀態:
    • Thread.sleep
    • Object.wait(long) with timeout
    • Thread.join(long) with timeout
    • LockSupport.parkNanos
    • LockSupport.parkUntil
  6. TERMINATED: 線程完成執行後結束的狀態。

jstack Dump日誌文件中的線程狀態

Monitor

Monitor是 Java中用以實現線程之間的互斥與協做的主要手段,它能夠當作是對象的鎖。每個對象都有,也僅有一個 monitor。bash

在HotSpot JVM中,monitor是由ObjectMonitor實現的,其主要數據結構以下(位於HotSpot虛擬機源碼ObjectMonitor.hpp文件,C++實現的):數據結構

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //記錄個數
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //處於wait狀態的線程,會被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //處於等block狀態的線程,會被加入到該列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }
複製代碼

ObjectMonitor中主要有如下4個參數:併發

  1. _Owner: 用於指向ObjectMonito對象的線程
  2. _EntrySet:用來保存處於blocked狀態的線程列表
  3. _WaitSet: 用來保存處於waiting狀態的線程
  4. _count: 計數器

當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合,當線程獲取到對象的monitor 後進入 _Owner 區域並把monitor中的owner變量設置爲當前線程。同時monitor中的計數器count加1,若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復爲null,count自減1,同時該線程進入 _WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其餘線程進入獲取monitor(鎖)。以下圖所示:post

Dump文件中的線程狀態

  1. Deadlock:死鎖線程,通常指多個線程調用間,進入相互資源佔用,致使一直等待沒法釋放的狀況。ui

  2. Runnable:通常指該線程正在執行狀態中,該線程佔用了資源 。spa

  3. Waiting on condition:等待資源,或等待某個條件的發生 。操作系統

  4. Blocked:線程阻塞,是指當前線程執行過程當中,所須要的資源長時間等待卻一直未能獲取到,被容器的線程管理器標識爲阻塞狀態,能夠理解爲等待資源超時的線程。

  5. Waiting for monitor entry :在線程嘗試執行同步代碼前,在monitor的」Entry Set「隊列中的等待線程。

  6. In Object.wait(): 當線程得到了 Monitor,進入了臨界區以後,若是發現線程繼續運行的條件沒有知足,它則調用對象(通常就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入 「Wait Set」隊列。

線程同步時的執行模型

  1. 任意一個對象都擁有本身的Monitor;
  2. 當這個對象由同步塊或者這個對象的同步方法調用時,執行方法的線程必須先獲取到該對象的監視器才能進入同步塊或者同步方法;
  3. 沒有獲取到監視器(執行該方法)的線程將會被阻塞在同步塊和同步方法的入口處,進入BLOCKED狀態。

下圖描述了對象、對象的監視器、同步隊列和執行線程之間的關係:

從圖中能夠看到

  1. 任意線程對Object(Object由synchronized保護)的訪問,首先要得到Object的監視器。
  2. 若是獲取成功,線程得到monitor,進入同步區域,執行同步塊。
  3. 若是獲取失敗,線程進入同步隊列,線程狀態變爲BLOCKED。
  4. 當訪問Object的前驅(得到了鎖的線程)釋放了鎖,則該釋放操做喚醒阻塞在同步隊列中的線程,使其從新嘗試對監視器的獲取。

線程等待/通知機制

等待/通知機制,是指一個線程A調用了對象O的wait()方法進入等待狀態,而另外一個線程B調用了對象O的notify()或者notifyAll()方法,線程A收到通知後從對象O的wait()方法返回,進而執行後續操做。上述兩個線程經過對象O來完成交互,而對象上的wait()和notify/notifyAll()的關係就如同開關信號同樣,用來完成等待方和通知方之間的交互工做。

等待/通知的相關方法是任意Java對象都具有的,由於這些方法被定義在全部對象的超類java.lang.Object上,方法和描述以下:

  1. notify(): 通知一個在對象上等待的線程,使其從wait()方法返回,而返回的前提是該線程獲取到了對象的鎖

  2. notifyAll(): 通知全部等待在該對象上的線程。

  3. wait(): 調用該方法的線程進入WAITING狀態,只有等待另外線程的通知或被中斷纔會返回,須要注意,調用wait()方法後,會釋放對象的鎖

  4. wait(long): 超時等待一段時間,這裏單位是毫秒。

  5. wait(long, int): 超時狀態更細粒度的控制,能夠達到納秒。

注意

  1. wait(), notify(), notifyAll()方法的調用都須要位於被synchronized關鍵字包裹的代碼塊或者是方法中,即線程須要獲取鎖才能執行這些方法
  2. wait() - notify(), wait() - notifyAll()的使用須要針對同一個鎖,否則會拋出異常。
  3. 調用wait()方法後,線程狀態由RUNNING變爲WAITING,並將當前線程放置到對象的等待隊列
  4. notify()或notifyAll()方法調用後,等待線程依舊不會從wait()返回,須要調用notify()或notifAll()的線程釋放鎖以後,等待線程纔有機會從wait()返回
  5. notify()方法將等待隊列中的一個等待線程從等待隊列中移到同步隊列中,而notifyAll()方法則是將等待隊列中全部的線程所有移到同步隊列,被移動的線程狀態由WAITING變爲BLOCKED
  6. 從wait()方法返回的前提是得到了調用對象的鎖

從上述能夠發現,等待/通知機制依託於同步機制,其目的就是確保等待線程從wait()方法返回時可以感知到通知線程對變量作出的修改。

下面是上述示例的過程圖:

  1. WaitThread首先獲取了對象的鎖,而後調用對象的wait()方法,從而放棄了鎖並進入了對象的等待隊列WaitQueue中,進入Waiting狀態。

  2. 因爲WaitThread釋放了對象的鎖,NotifyThread隨後獲取了對象的鎖,並調用對象的notify()方法,將WaitThread從WaitQueue移到SynchronizedQueue中,此時WaitThread的狀態變爲Blocked狀態。

  3. NotifyThread釋放了鎖以後,WaitThread再次獲取到鎖並從wait()方法返回繼續執行。

等待/通知的經典範式

等待/通知的經典範式分爲兩個部分:等待方和通知方。 等待方遵循以下原則:

  1. 獲取對象的鎖。
  2. 若是條件不知足,那麼調用對象的wait()方法,被通知後仍要檢查條件。
  3. 條件知足則執行對應的邏輯。 對應的僞代碼以下:
synchronized(對象) {
	while(條件不知足) {
		對象.wait();
	}
	對應的處理邏輯
}
複製代碼

通知方遵循以下原則:

  1. 得到對象的鎖。
  2. 改變條件。
  3. 通知全部等待在對象上的線程。 對應的僞代碼以下:
synchronized(對象) {
	改變條件
	對象.notifyAll();
}
複製代碼

Thread.join()

這部份內容能夠參見深刻理解Java併發編程之經過JDK C++源碼以及Debug源碼死扣Thread.join()

ThreadLocal

這部份內容能夠參見深刻理解Java併發編程之把ThreadLocal扣爛

參考與感謝

  1. blog.csdn.net/lengxiao199…
  2. blog.csdn.net/javazejian/…
  3. www.cnblogs.com/zhengyun_us…
相關文章
相關標籤/搜索