Java并發程式設計:Thread類的使用
在前面2篇文章分别講到了線程和程序的由來、以及如何在Java中怎麼建立線程和程序。今天我們來學習一下Thread類,在學習Thread類之前,先介紹與線程相關知識:線程的幾種狀态、上下文切換,然後接着介紹Thread類中的方法的具體使用。
以下是本文的目錄大綱:
一.線程的狀态
二.上下文切換
三.Thread類中的方法
若有不正之處,請多多諒解并歡迎批評指正。
請尊重作者勞動成果,轉載請标明原文連結:
http://www.cnblogs.com/dolphin0520/p/3920357.html
在正式學習Thread類中的具體方法之前,我們先來了解一下線程有哪些狀态,這個将會有助于後面對Thread類中的方法的了解。
線程從建立到最終的消亡,要經曆若幹個狀态。一般來說,線程包括以下這幾個狀态:建立(new)、就緒(runnable)、運作(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。
當需要新起一個線程來執行某個子任務時,就建立了一個線程。但是線程建立之後,不會立即進入就緒狀态,因為線程的運作需要一些條件(比如記憶體資源,在前面的JVM記憶體區域劃分一篇博文中知道程式計數器、Java棧、本地方法棧都是線程私有的,是以需要為線程配置設定一定的記憶體空間),隻有線程運作需要的所有條件滿足了,才進入就緒狀态。
當線程進入就緒狀态後,不代表立刻就能擷取CPU執行時間,也許此時CPU正在執行其他的事情,是以它要等待。當得到CPU執行時間之後,線程便真正進入運作狀态。
線程在運作狀态過程中,可能有多個原因導緻目前線程不繼續運作下去,比如使用者主動讓線程睡眠(睡眠一定的時間之後再重新執行)、使用者主動讓線程等待,或者被同步塊給阻塞,此時就對應着多個狀态:time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)。
當由于突然中斷或者子任務執行完畢,線程就會被消亡。
下面這副圖描述了線程從建立到消亡之間的狀态:

在有些教程上将blocked、waiting、time waiting統稱為阻塞狀态,這個也是可以的,隻不過這裡我想将線程的狀态和Java中的方法調用聯系起來,是以将waiting和time waiting兩個狀态分離出來。
對于單核CPU來說(對于多核CPU,此處就了解為一個核),CPU在一個時刻隻能運作一個線程,當在運作一個線程的過程中轉去運作另外一個線程,這個叫做線程上下文切換(對于程序也是類似)。
由于可能目前線程的任務并沒有執行完畢,是以在切換時需要儲存線程的運作狀态,以便下次重新切換回來時能夠繼續切換之前的狀态運作。舉個簡單的例子:比如一個線程A正在讀取一個檔案的内容,正讀到檔案的一半,此時需要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,我們不希望線程A又從檔案的開頭來讀取。
是以需要記錄線程A的運作狀态,那麼會記錄哪些資料呢?因為下次恢複時需要知道在這之前目前線程已經執行到哪條指令了,是以需要記錄程式計數器的值,另外比如說線程正在進行某個計算的時候被挂起了,那麼下次繼續執行的時候需要知道之前挂起時變量的值時多少,是以需要記錄CPU寄存器的狀态。是以一般來說,線程上下文切換過程中會記錄程式計數器、CPU寄存器狀态等資料。
說簡單點的:對于線程的上下文切換實際上就是 存儲和恢複CPU狀态的過程,它使得線程執行能夠從中斷點恢複執行。
雖然多線程可以使得任務執行的效率得到提升,但是由于線上程切換時同樣會帶來一定的開銷代價,并且多個線程會導緻系統資源占用的增加,是以在進行多線程程式設計時要注意這些因素。
通過檢視java.lang.Thread類的源碼可知:
Thread類實作了Runnable接口,在Thread類中,有一些比較關鍵的屬性,比如name是表示Thread的名字,可以通過Thread類的構造器中的參數來指定線程名字,priority表示線程的優先級(最大值為10,最小值為1,預設值為5),daemon表示線程是否是守護線程,target表示要執行的任務。
下面是Thread類中常用的方法:
以下是關系到線程運作狀态的幾個方法:
1)start方法
start()用來啟動一個線程,當調用start方法後,系統才會開啟一個新的線程來執行使用者定義的子任務,在這個過程中,會為相應的線程配置設定需要的資源。
2)run方法
run()方法是不需要使用者來調用的,當通過start方法啟動一個線程之後,當線程獲得了CPU執行時間,便進入run方法體去執行具體的任務。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執行的任務。
3)sleep方法
sleep方法有兩個重載版本:
1
2
3
<code>sleep(</code><code>long</code> <code>millis) </code><code>//參數為毫秒</code>
<code>sleep(</code><code>long</code> <code>millis,</code><code>int</code> <code>nanoseconds) </code><code>//第一參數為毫秒,第二個參數為納秒</code>
sleep相當于讓線程睡眠,交出CPU,讓CPU去執行其他的任務。
但是有一點要非常注意,sleep方法不會釋放鎖,也就是說如果目前線程持有對某個對象的鎖,則即使調用sleep方法,其他線程也無法通路這個對象。看下面這個例子就清楚了:
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<code>public</code> <code>class</code> <code>Test {</code>
<code> </code>
<code> </code><code>private</code> <code>int</code> <code>i = </code><code>10</code><code>;</code>
<code> </code><code>private</code> <code>Object object = </code><code>new</code> <code>Object();</code>
<code> </code><code>public</code> <code>static</code> <code>void</code> <code>main(String[] args) </code><code>throws</code> <code>IOException {</code>
<code> </code><code>Test test = </code><code>new</code> <code>Test();</code>
<code> </code><code>MyThread thread1 = test.</code><code>new</code> <code>MyThread();</code>
<code> </code><code>MyThread thread2 = test.</code><code>new</code> <code>MyThread();</code>
<code> </code><code>thread1.start();</code>
<code> </code><code>thread2.start();</code>
<code> </code><code>} </code>
<code> </code><code>class</code> <code>MyThread </code><code>extends</code> <code>Thread{</code>
<code> </code><code>@Override</code>
<code> </code><code>public</code> <code>void</code> <code>run() {</code>
<code> </code><code>synchronized</code> <code>(object) {</code>
<code> </code><code>i++;</code>
<code> </code><code>System.out.println(</code><code>"i:"</code><code>+i);</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>System.out.println(</code><code>"線程"</code><code>+Thread.currentThread().getName()+</code><code>"進入睡眠狀态"</code><code>);</code>
<code> </code><code>Thread.currentThread().sleep(</code><code>10000</code><code>);</code>
<code> </code><code>} </code><code>catch</code> <code>(InterruptedException e) {</code>
<code> </code><code>// TODO: handle exception</code>
<code> </code><code>}</code>
<code> </code><code>System.out.println(</code><code>"線程"</code><code>+Thread.currentThread().getName()+</code><code>"睡眠結束"</code><code>);</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code> </code><code>}</code>
<code>}</code>
輸出結果:
從上面輸出結果可以看出,當Thread-0進入睡眠狀态之後,Thread-1并沒有去執行具體的任務。隻有當Thread-0執行完之後,此時Thread-0釋放了對象鎖,Thread-1才開始執行。
注意,如果調用了sleep方法,必須捕獲InterruptedException異常或者将該異常向上層抛出。當線程睡眠時間滿後,不一定會立即得到執行,因為此時可能CPU正在執行其他的任務。是以說調用sleep方法相當于讓線程進入阻塞狀态。
4)yield方法
調用yield方法會讓目前線程交出CPU權限,讓CPU去執行其他的線程。它跟sleep方法類似,同樣不會釋放鎖。但是yield不能控制具體的交出CPU的時間,另外,yield方法隻能讓擁有相同優先級的線程有擷取CPU執行時間的機會。
注意,調用yield方法并不會讓線程進入阻塞狀态,而是讓線程重回就緒狀态,它隻需要等待重新擷取CPU執行時間,這一點是和sleep方法不一樣的。
5)join方法
join方法有三個重載版本:
<code>join()</code>
<code>join(</code><code>long</code> <code>millis) </code><code>//參數為毫秒</code>
<code>join(</code><code>long</code> <code>millis,</code><code>int</code> <code>nanoseconds) </code><code>//第一參數為毫秒,第二個參數為納秒</code>
假如在main線程中,調用thread.join方法,則main方法會等待thread線程執行完畢或者等待一定的時間。如果調用的是無參join方法,則等待thread執行完畢,如果調用的是指定了時間參數的join方法,則等待一定的事件。
看下面一個例子:
<code> </code><code>System.out.println(</code><code>"進入線程"</code><code>+Thread.currentThread().getName());</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>System.out.println(</code><code>"線程"</code><code>+Thread.currentThread().getName()+</code><code>"等待"</code><code>);</code>
<code> </code><code>thread1.join();</code>
<code> </code><code>System.out.println(</code><code>"線程"</code><code>+Thread.currentThread().getName()+</code><code>"繼續執行"</code><code>);</code>
<code> </code><code>} </code><code>catch</code> <code>(InterruptedException e) {</code>
<code> </code><code>// TODO Auto-generated catch block</code>
<code> </code><code>e.printStackTrace();</code>
<code> </code><code>System.out.println(</code><code>"進入線程"</code><code>+Thread.currentThread().getName());</code>
<code> </code><code>try</code> <code>{</code>
<code> </code><code>Thread.currentThread().sleep(</code><code>5000</code><code>);</code>
<code> </code><code>} </code><code>catch</code> <code>(InterruptedException e) {</code>
<code> </code><code>// TODO: handle exception</code>
<code> </code><code>System.out.println(</code><code>"線程"</code><code>+Thread.currentThread().getName()+</code><code>"執行完畢"</code><code>);</code>
可以看出,當調用thread1.join()方法後,main線程會進入等待,然後等待thread1執行完之後再繼續執行。
實際上調用join方法是調用了Object的wait方法,這個可以通過檢視源碼得知:
wait方法會讓線程進入阻塞狀态,并且會釋放線程占有的鎖,并交出CPU執行權限。
由于wait方法會讓線程釋放對象鎖,是以join方法同樣會讓線程釋放對一個對象持有的鎖。具體的wait方法使用在後面文章中給出。
6)interrupt方法
interrupt,顧名思義,即中斷的意思。單獨調用interrupt方法可以使得處于阻塞狀态的線程抛出一個異常,也就說,它可以用來中斷一個正處于阻塞狀态的線程;另外,通過interrupt方法和isInterrupted()方法來停止正在運作的線程。
下面看一個例子:
<code> </code><code>MyThread thread = test.</code><code>new</code> <code>MyThread();</code>
<code> </code><code>thread.start();</code>
<code> </code><code>Thread.currentThread().sleep(</code><code>2000</code><code>);</code>
<code> </code>
<code> </code><code>thread.interrupt();</code>
<code> </code><code>System.out.println(</code><code>"進入睡眠狀态"</code><code>);</code>
<code> </code><code>Thread.currentThread().sleep(</code><code>10000</code><code>);</code>
<code> </code><code>System.out.println(</code><code>"睡眠完畢"</code><code>);</code>
<code> </code><code>System.out.println(</code><code>"得到中斷異常"</code><code>);</code>
<code> </code><code>System.out.println(</code><code>"run方法執行完畢"</code><code>);</code>
從這裡可以看出,通過interrupt方法可以中斷處于阻塞狀态的線程。那麼能不能中斷處于非阻塞狀态的線程呢?看下面這個例子:
<code> </code><code>int</code> <code>i = </code><code>0</code><code>;</code>
<code> </code><code>while</code><code>(i<Integer.MAX_VALUE){</code>
<code> </code><code>System.out.println(i+</code><code>" while循環"</code><code>);</code>
運作該程式會發現,while循環會一直運作直到變量i的值超出Integer.MAX_VALUE。是以說直接調用interrupt方法不能中斷正在運作中的線程。
但是如果配合isInterrupted()能夠中斷正在運作的線程,因為調用interrupt方法相當于将中斷标志位置為true,那麼可以通過調用isInterrupted()判斷中斷标志是否被置位來中斷線程的執行。比如下面這段代碼:
<code> </code><code>while</code><code>(!isInterrupted() && i<Integer.MAX_VALUE){</code>
運作會發現,列印若幹個值之後,while循環就停止列印了。
但是一般情況下不建議通過這種方式來中斷線程,一般會在MyThread類中增加一個屬性 isStop來标志是否結束while循環,然後再在while循環中判斷isStop的值。
<code>class</code> <code>MyThread </code><code>extends</code> <code>Thread{</code>
<code> </code><code>private</code> <code>volatile</code> <code>boolean</code> <code>isStop = </code><code>false</code><code>;</code>
<code> </code><code>while</code><code>(!isStop){</code>
<code> </code>
<code> </code><code>public</code> <code>void</code> <code>setStop(</code><code>boolean</code> <code>stop){</code>
<code> </code><code>this</code><code>.isStop = stop;</code>
那麼就可以在外面通過調用setStop方法來終止while循環。
7)stop方法
stop方法已經是一個廢棄的方法,它是一個不安全的方法。因為調用stop方法會直接終止run方法的調用,并且會抛出一個ThreadDeath錯誤,如果線程持有某個對象鎖的話,會完全釋放鎖,導緻對象狀态不一緻。是以stop方法基本是不會被用到的。
8)destroy方法
destroy方法也是廢棄的方法。基本不會被使用到。
以下是關系到線程屬性的幾個方法:
1)getId
用來得到線程ID
2)getName和setName
用來得到或者設定線程名稱。
3)getPriority和setPriority
用來擷取和設定線程優先級。
4)setDaemon和isDaemon
用來設定線程是否成為守護線程和判斷線程是否是守護線程。
守護線程和使用者線程的差別在于:守護線程依賴于建立它的線程,而使用者線程則不依賴。舉個簡單的例子:如果在main線程中建立了一個守護線程,當main方法運作完畢之後,守護線程也會随着消亡。而使用者線程則不會,使用者線程會一直運作直到其運作完畢。在JVM中,像垃圾收集器線程就是守護線程。
Thread類有一個比較常用的靜态方法currentThread()用來擷取目前線程。
在上面已經說到了Thread類中的大部分方法,那麼Thread類中的方法調用到底會引起線程狀态發生怎樣的變化呢?下面一幅圖就是在上面的圖上進行改進而來的:
參考資料:
《Java程式設計思想》
本文轉載自海 子部落格園部落格,原文連結:http://www.cnblogs.com/dolphin0520/p/3920357.html如需轉載自行聯系原作者