Java Thread 的使用

1、線程的狀態

在正式學習Thread類中的具體方法以前,咱們先來了解一下線程有哪些狀態,這個將會有助於後面對Thread類中的方法的理解。html

線程從建立到最終的消亡,要經歷若干個狀態。通常來講,線程包括如下這幾個狀態:建立(new)、就緒(runnable)、運行(running)、阻塞(blocked)、time waiting、waiting、消亡(dead)。java

當須要新起一個線程來執行某個子任務時,就建立了一個線程。可是線程建立以後,不會當即進入就緒狀態,由於線程的運行須要一些條件(好比內存資源,在前面的JVM內存區域劃分一篇博文中知道程序計數器、Java棧、本地方法棧都是線程私有的,因此須要爲線程分配必定的內存空間),只有線程運行須要的全部條件知足了,才進入就緒狀態。編程

當線程進入就緒狀態後,不表明馬上就能獲取CPU執行時間,也許此時CPU正在執行其餘的事情,所以它要等待。當獲得CPU執行時間以後,線程便真正進入運行狀態。安全

線程在運行狀態過程當中,可能有多個緣由致使當前線程不繼續運行下去,好比用戶主動讓線程睡眠(睡眠必定的時間以後再從新執行)、用戶主動讓線程等待,或者被同步塊給阻塞,此時就對應着多個狀態:time waiting(睡眠或等待必定的事件)、waiting(等待被喚醒)、blocked(阻塞)。多線程

當因爲忽然中斷或者子任務執行完畢,線程就會被消亡。ide

下面這副圖描述了線程從建立到消亡之間的狀態:學習

在有些教程上將blocked、waiting、time waiting統稱爲阻塞狀態,這個也是能夠的,只不過這裏我想將線程的狀態和Java中的方法調用聯繫起來,因此將waiting和time waiting兩個狀態分離出來。this

2、上下文切換

對於單核CPU來講(對於多核CPU,此處就理解爲一個核),CPU在一個時刻只能運行一個線程,當在運行一個線程的過程當中轉去運行另一個線程,這個叫作線程上下文切換(對於進程也是相似)。spa

因爲可能當前線程的任務並無執行完畢,因此在切換時須要保存線程的運行狀態,以便下次從新切換回來時可以繼續切換以前的狀態運行。舉個簡單的例子:好比一個線程A正在讀取一個文件的內容,正讀到文件的一半,此時須要暫停線程A,轉去執行線程B,當再次切換回來執行線程A的時候,咱們不但願線程A又從文件的開頭來讀取。.net

所以須要記錄線程A的運行狀態,那麼會記錄哪些數據呢?由於下次恢復時須要知道在這以前當前線程已經執行到哪條指令了,因此須要記錄程序計數器的值,另外好比說線程正在進行某個計算的時候被掛起了,那麼下次繼續執行的時候須要知道以前掛起時變量的值時多少,所以須要記錄CPU寄存器的狀態。因此通常來講,線程上下文切換過程當中會記錄程序計數器、CPU寄存器狀態等數據。

說簡單點的:對於線程的上下文切換實際上就是 存儲和恢復CPU狀態的過程,它使得線程執行可以從中斷點恢復執行。

雖然多線程可使得任務執行的效率獲得提高,可是因爲在線程切換時一樣會帶來必定的開銷代價,而且多個線程會致使系統資源佔用的增長,因此在進行多線程編程時要注意這些因素。

3、Thread類中的方法

經過查看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方法有兩個重載版本:

sleep(long millis)     //參數爲毫秒 sleep(long millis,int nanoseconds)    //第一參數爲毫秒,第二個參數爲納秒

sleep至關於讓線程睡眠,交出CPU,讓CPU去執行其餘的任務。

可是有一點要很是注意,sleep方法不會釋放鎖,也就是說若是當前線程持有對某個對象的鎖,則即便調用sleep方法,其餘線程也沒法訪問這個對象。看下面這個例子就清楚了:

public class Test { private int i = 10; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread1 = test.new MyThread(); MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("線程"+Thread.currentThread().getName()+"進入睡眠狀態"); Thread.currentThread().sleep(10000); } catch (InterruptedException e) { // TODO: handle exception
 } System.out.println("線程"+Thread.currentThread().getName()+"睡眠結束"); i++; System.out.println("i:"+i); } } } }

 輸出結果:

從上面輸出結果能夠看出,當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方法有三個重載版本:

join() join(long millis)     //參數爲毫秒
join(long millis,int nanoseconds)    //第一參數爲毫秒,第二個參數爲納秒

假如在main線程中,調用thread.join方法,則main方法會等待thread線程執行完畢或者等待必定的時間。若是調用的是無參join方法,則等待thread執行完畢,若是調用的是指定了時間參數的join方法,則等待必定的事件。

看下面一個例子:

public class Test { public static void main(String[] args) throws IOException { System.out.println("進入線程"+Thread.currentThread().getName()); Test test = new Test(); MyThread thread1 = test.new MyThread(); thread1.start(); try { System.out.println("線程"+Thread.currentThread().getName()+"等待"); thread1.join(); System.out.println("線程"+Thread.currentThread().getName()+"繼續執行"); } catch (InterruptedException e) { // TODO Auto-generated catch block
 e.printStackTrace(); } } class MyThread extends Thread{ @Override public void run() { System.out.println("進入線程"+Thread.currentThread().getName()); try { Thread.currentThread().sleep(5000); } catch (InterruptedException e) { // TODO: handle exception
 } System.out.println("線程"+Thread.currentThread().getName()+"執行完畢"); } } }

輸出結果:

能夠看出,當調用thread1.join()方法後,main線程會進入等待,而後等待thread1執行完以後再繼續執行。

實際上調用join方法是調用了Object的wait方法,這個能夠經過查看源碼得知:

wait方法會讓線程進入阻塞狀態,而且會釋放線程佔有的鎖,並交出CPU執行權限。

因爲wait方法會讓線程釋放對象鎖,因此join方法一樣會讓線程釋放對一個對象持有的鎖。具體的wait方法使用在後面文章中給出。

6)interrupt方法

interrupt,顧名思義,即中斷的意思。單獨調用interrupt方法可使得處於阻塞狀態的線程拋出一個異常,也就說,它能夠用來中斷一個正處於阻塞狀態的線程;另外,經過interrupt方法和isInterrupted()方法來中止正在運行的線程。

下面看一個例子:

public class Test { public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread = test.new MyThread(); thread.start(); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { } thread.interrupt(); } class MyThread extends Thread{ @Override public void run() { try { System.out.println("進入睡眠狀態"); Thread.currentThread().sleep(10000); System.out.println("睡眠完畢"); } catch (InterruptedException e) { System.out.println("獲得中斷異常"); } System.out.println("run方法執行完畢"); } } }

輸出結果:

從這裏能夠看出,經過interrupt方法能夠中斷處於阻塞狀態的線程。那麼能不能中斷處於非阻塞狀態的線程呢?看下面這個例子:

public class Test { public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread = test.new MyThread(); thread.start(); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { } thread.interrupt(); } class MyThread extends Thread{ @Override public void run() { int i = 0; while(i<Integer.MAX_VALUE){ System.out.println(i+" while循環"); i++; } } } }

運行該程序會發現,while循環會一直運行直到變量i的值超出Integer.MAX_VALUE。因此說直接調用interrupt方法不能中斷正在運行中的線程。

可是若是配合isInterrupted()可以中斷正在運行的線程,由於調用interrupt方法至關於將中斷標誌位置爲true,那麼能夠經過調用isInterrupted()判斷中斷標誌是否被置位來中斷線程的執行。好比下面這段代碼:

public class Test { public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread = test.new MyThread(); thread.start(); try { Thread.currentThread().sleep(2000); } catch (InterruptedException e) { } thread.interrupt(); } class MyThread extends Thread{ @Override public void run() { int i = 0; while(!isInterrupted() && i<Integer.MAX_VALUE){ System.out.println(i+" while循環"); i++; } } } }

運行會發現,打印若干個值以後,while循環就中止打印了。

可是通常狀況下不建議經過這種方式來中斷線程,通常會在MyThread類中增長一個屬性 isStop來標誌是否結束while循環,而後再在while循環中判斷isStop的值。

class MyThread extends Thread{ private volatile boolean isStop = false; @Override public void run() { int i = 0; while(!isStop){ i++; } } public void setStop(boolean stop){ this.isStop = stop; } }

那麼就能夠在外面經過調用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://zy19982004.iteye.com/blog/1626916

http://www.cnblogs.com/DreamSea/archive/2012/01/11/JavaThread.html#navigation

http://www.blogjava.net/vincent/archive/2008/08/23/223912.html

http://iteye.blog.163.com/blog/static/1863080962012111424544215/

http://blog.csdn.net/lifei128/article/details/20363257

相關文章
相關標籤/搜索