天天看點

并發程式設計:多線程設計原理

作者:日拱一卒程式猿

一、Thread和Runnable

1、java中的線程

建立執行線程有兩種方法: 擴充Thread 類。 實作Runnable 接口。

2、Java中的線程:特征和狀态

1. 所有的Java 程式,不論并發與否,都有一個名為主線程的Thread 對象。執行該程式時, Java 虛拟機( JVM )将建立一個新Thread 并在該線程中執行main()方法。這是非并發應用程式中 唯一的線程,也是并發應用程式中的第一個線程。

2. Java中的線程共享應用程式中的所有資源,包括記憶體和打開的檔案,快速而簡單地共享資訊。 但是必須使用同步避免資料競争。

3. Java中的所有線程都有一個優先級,這個整數值介于Thread.MIN_PRIORITY(1)和 Thread.MAX_PRIORITY(10)之間,預設優先級是Thread.NORM_PRIORITY(5)。線程的 執行順序并沒有保證,通常,較高優先級的線程将在較低優先級的錢程之前執行。

4. 在Java 中,可以建立兩種線程: 守護線程。 非守護線程。 差別在于它們如何影響程式的結束。 Java程式結束執行過程的情形: 程式執行Runtime類的exit()方法, 而且使用者有權執行該方法。 應用程式的所有非守護線程均已結束執行,無論是否有正在運作的守護線程。 守護線程通常用在作為垃圾收集器或緩存管理器的應用程式中,執行輔助任務。線上程start之前調 用isDaemon()方法檢查線程是否為守護線程,也可以使用setDaemon()方法将某個線程确立為守護線程。

5. Thread.States類中定義線程的狀态如下: NEW:Thread對象已經建立,但是還沒有開始執行。 RUNNABLE:Thread對象正在Java虛拟機中運作。 BLOCKED : Thread對象正在等待鎖定。 WAITING:Thread 對象正在等待另一個線程的動作。 TIME_WAITING:Thread對象正在等待另一個線程的操作,但是有時間限制。 TERMINATED:Thread對象已經完成了執行。 getState()方法擷取Thread對象的狀态,可以直接更改線程的狀态。 在給定時間内, 線程隻能處于一個狀态。這些狀态是JVM使用的狀态,不能映射到作業系統的線程 狀态。

3、Thread類和Runnable 接口

Runnable接口隻定義了一種方法:run()方法。這是每個線程的主方法。當執行start()方法啟動新線 程時,它将調用run()方法。

Thread類其他常用方法: 擷取和設定Thread對象資訊的方法。

getId():該方法傳回Thread對象的辨別符。該辨別符是在錢程建立時配置設定的一個正 整數。線上程的整個生命周期中是唯一且無法改變的。

getName()/setName():這兩種方法允許你擷取或設定Thread對象的名稱。這個名 稱是一個String對象,也可以在Thread類的構造函數中建立。

getPriority()/setPriority():你可以使用這兩種方法來擷取或設定Thread對象的優先 級。

isDaemon()/setDaemon():這兩種方法允許你擷取或建立Thread對象的守護條件。

getState():該方法傳回Thread對象的狀态。

interrupt():中斷目标線程,給目标線程發送一個中斷信号,線程被打上中斷标記。

interrupted():判斷目标線程是否被中斷,但是将清除線程的中斷标記。

isinterrupted():判斷目标線程是否被中斷,不會清除中斷标記。

sleep(long ms):該方法将線程的執行暫停ms時間。

join():暫停線程的執行,直到調用該方法的線程執行結束為止。可以使用該方法等待另一個 Thread對象結束。

setUncaughtExceptionHandler():當線程執行出現未校驗異常時,該方法用于建立未校驗異 常的控制器。

currentThread():Thread類的靜态方法,傳回實際執行該代碼的Thread對象。

4、Callable

Callable 接口是一個與Runnable 接口非常相似的接口。Callable 接口的主要特征如下。

  • 接口。有簡單類型參數,與call()方法的傳回類型相對應。
  • 聲明了call()方法。執行器運作任務時,該方法會被執行器執行。它必須傳回聲明中指定類型的 對象。
  • call()方法可以抛出任何一種校驗異常。可以實作自己的執行器并重載afterExecute()方法來處 理這些異常。

5、synchronized關鍵字

(1)鎖的對象

synchronized關鍵字“給某個對象加鎖”,示例代碼:

并發程式設計:多線程設計原理

等價于:

并發程式設計:多線程設計原理

執行個體方法的鎖加在對象myClass上;靜态方法的鎖加在MyClass.class上。

6、鎖的本質

如果一份資源需要多個線程同時通路,需要給該資源加鎖。加鎖之後,可以保證同一時間隻能有一個線程通路該資源。資源可以是一個變量、一個對象或一個檔案等。

并發程式設計:多線程設計原理

鎖是一個“對象”,作用如下:

1. 這個對象内部得有一個标志位(state變量),記錄自己有沒有被某個線程占用。最簡單的情況 是這個state有0、1兩個取值,0表示沒有線程占用這個鎖,1表示有某個線程占用了這個鎖。

2. 如果這個對象被某個線程占用,記錄這個線程的thread ID。

3. 這個對象維護一個thread id list,記錄其他所有阻塞的、等待擷取拿這個鎖的線程。在目前線 程釋放鎖之後從這個thread id list裡面取一個線程喚醒。

要通路的共享資源本身也是一個對象,例如前面的對象myClass,這兩個對象可以合成一個對象。 代碼就變成synchronized(this) {…},要通路的共享資源是對象a,鎖加在對象a上。當然,也可以另外建立一個對象,代碼變成synchronized(obj1) {…}。這個時候,通路的共享資源是對象a,而鎖加在建立的對象obj1上。

資源和鎖合二為一,使得在Java裡面,synchronized關鍵字可以加在任何對象的成員上面。這意味着,這個對象既是共享資源,同時也具備“鎖”的功能!

鎖的實作原理:

在對象頭裡,有一塊資料叫Mark Word。在64位機器上,Mark Word是8位元組(64位)的,這64位 中有2個重要字段:鎖标志位和占用該鎖的thread ID。因為不同版本的JVM實作,對象頭的資料結構會有各種差異。

繼續閱讀