轉轉請注明出處:http://blog.csdn.net/yegongheng/article/details/38708765
上一篇《線程的概念及簡單實現》博文我們簡單地認識了關于多線程的概念以及使用Java語言實現多線程,這算是我們對Java并發編程學習的一個入門吧,那本篇博文我們將繼續更深入地學習多線程方面的知識。
同樣的,線程作為一項任務的執行者,從開啟、運行到終結、銷毀,都有它自己的生命周期。那在Java定義中,一般線程可以分為六種狀態,我們通過查看Thread類源碼發現,在Thread類中定義了一個線程狀態的枚舉,代碼如下:
[java]view plaincopy
publicenumState?{
/**
*?Thread?state?for?a?thread?which?has?not?yet?started.
*/
NEW,
/**
*?...
*/
RUNNABLE,
/**
*?...
*/
BLOCKED,
/**
*?...
*/
WAITING,
/**
*...
*/
TIMED_WAITING,
/**
*?Thread?state?for?a?terminated?thread.
*?The?thread?has?completed?execution.
*/
TERMINATED;
}
下面我們對每一種線程狀態進行簡要的說明:
1.新生(New):在Java中使用new Thread(r)來創建一個新的線程,此時線程處于新生的狀態,但是程序還沒有開始運行線程的代碼;
2.可運行(Runnable):在Java中當調用start()方法后,線程進入了可運行的狀態,此時線程可能正在運行,也可能沒有運行,這個取決于CPU是否給當前線程提供了時間片;
3.阻塞(Blocked):一般在如下情況下,線程會處于阻塞狀態:當線程想要獲取內部的對象鎖,而該鎖被其它線程持有,此時線程陷入阻塞;
4.等待(Wait):當線程調用Thread.join()方法將CPU的執行權力(即時間片執行權力)出讓給其它線程時或調用wait()方法讓當前線程等待其它線程資源執行時,線程處于等待狀態。阻塞狀態和等待狀態的區別還是比較大的。
5.計時等待(Timed waiting):計時等待和上面所說的等待狀態類似,不同的是計時等待是在其所設置的超時時間后從等待狀態自動自我喚醒,然后進入到可運行狀態,而前者是需要其它線程進行手動喚醒。那一般設置定時等待的方法有sleep(long millis)、wait(long timeout)或join(long millis)。
6.終止(Terminated) :而線程終止或銷毀一般是會在以下兩種情況下發生:1. run()方法中的所有耗時操作都執行完畢,程序流正常退出;2. 在run()方法中有未捕獲的異常而導致程序的非正常退出。
基本了解了線程的六種狀態后,下面我們通過一張線程的狀態圖來進步了解一下線程六種狀態之間的關系和切換過程,如圖下:
線程根據作用可以分為用戶線程和守護線程,一般我們平時用于執行具體業務邏輯的線程都是用戶線程,而守護線程則一般是為正在運行的用戶線程提供便利服務的。比方說JVM中的GC(垃圾回收器)線程,它就是一個守護線程,它的作用就是回收其它普通用戶線程執行完成后遺留下的內存資源。那我們就會想,若在JVM中的普通用戶線程執行完各自的業務邏輯后消亡,守護線程依然在運行,那JVM實例還有存在的必要嗎?答案當然是 -- NO。JVM實例不會因為守護線程沒有終止而繼續存在,它會在所有普通用戶線程終止后退出。
那既然JVM實例并不會因為守護線程正在運行而持續保持,那為了避免操作和對象的完整性,我們不應該將一些較為重要的業務邏輯放在守護線程中執行,如文件、數據庫的操作,因為它有可能在任何時候甚至在一個操作的中間發生中斷。守護線程并不僅僅存在于系統級別,我們自己也可以創建守護線程,我們只需要Thread對象在調用start()方法之前調用setDaemon(boolean on)方法,將參數設置為true,那我們所創建的線程便是守護線程。
談到線程的優先級,可能許多讀者認為這部分知識并沒有單獨拿出來學習的必要,因為在java中,線程的優先級無非就是為線程設置從1-10之間的線程等級,在程序運行時,線程調度器會首先選擇具有較高優先等級的線程。其實上面讀者對線程優先級的理解并沒有錯,但理解得并不是很深刻和全面,因為線程的優先級是高度依賴系統的,不同的操作系統平臺的線程實現機制是不同的。就拿Windows 和Linux操作系統來講,Windows系統有7個線程優先級別,而Linux系統擁有2的31次方線程優先級別。當Java虛擬機依賴于宿主主機平臺(Linux或Windows)的線程實現機制時,java線程的優先級會被映射到宿主主機平臺的優先級上,對于擁有2的31次方優先等級的Linux系統來說,支持映射java 10個線程優先等級當然沒有問題,但對于只有7個線程優先等級的Windows系統來說,要映射到Java的10個線程優先等級上,勢必會造成很多問題,例如可能Java里面的優先級1、2會等同于Windows系統里的1等級,或是線程優先級8、9、10等同于Windows系統里的7等級。那此種情況下,Java的線程優先級的設置就沒什么作用了。
另外Java程序是運行在Java虛擬機中的,而由于虛擬機版本的差異化,也會導致線程優先級的映射產生不同的結果,例如Sun為Linux提供的Java虛擬機,線程的優先級被忽略--即所有線程具有相同的優先級。
前面講了那么多文縐縐的理論知識,自己都感覺有點磨嘰了,下面我們來學習如何使用Java為線程設置優先級。一般我們可以通過調用setPriority(int newPriority)方法為線程設置優先級,方法中傳遞1-10之間的常量值,用于設定線程優先等級,1為最低優先級(在Java中可以用Thread.MIN_PRIORITY),10為最高優先級(在程序中可以用Thread.MAX_PRIORITY),如何不設置的話,默認線程等級為5(在程序中可以用Thread.NORM_PRIORITY)。下面這個實驗,我們創建三個線程,模擬三個售票窗口售票,并且為每個線程設置不同的優先級,觀察在設置了優先級后,每個售票窗口售票情況是否有明顯的差別。我們先來看下代碼,如下:
[java]view plaincopy
packagecom.androidleaf.multithreading.priority;
publicclassThreadPriority?{
publicstaticvoidmain(String[]?args)?{
//?TODO?Auto-generated?method?stub
MyRunnable?myRunnable?=newMyRunnable();
Thread?mThread0?=newThread(myRunnable);
//設置最高優先級,10
mThread0.setPriority(Thread.MAX_PRIORITY);
Thread?mThread1?=newThread(myRunnable);
//默認優先級,5
mThread1.setPriority(Thread.NORM_PRIORITY);
Thread?mThread2?=newThread(myRunnable);
//設置最低優先級,1
mThread2.setPriority(Thread.MIN_PRIORITY);
mThread0.start();
mThread1.start();
mThread2.start();
}
}
classMyRunnableimplementsRunnable{
privateinttickets?=50000;
privateintthreadFirst?=0;
privateintthreadSecond?=0;
privateintthreadThird?=0;
@Override
publicvoidrun()?{
//?TODO?Auto-generated?method?stub
while(true){
synchronized(this)?{
if(tickets?>0){
System.out.println("總票數:"+?tickets?+"??窗口號:"
+?Thread.currentThread().getName()
+"?出售1張????????剩下票數:"+?--tickets);
if(Thread.currentThread().getName().equals("Thread-0")){
++?threadFirst;
}elseif(Thread.currentThread().getName().equals("Thread-1")){
++?threadSecond;
}else{
++?threadThird;
}
}else{
break;
}
}
}
//售票次數只打印一次
if(Thread.currentThread().getName().equals("Thread-0")){
System.out.println("第一售票窗口售票數:"+?threadFirst);
System.out.println("第二售票窗口售票數:"+?threadSecond);
System.out.println("第三售票窗口售票數:"+?threadThird);
}
}
}
程序中,為了讓測試結果更具有可靠性,我們盡量讓售票數較大(這里取50000張),然后三個窗口開始售票,最后當售票結束后,統計每個窗口的售票數。我們通過數十次的測試后,取得了一組售票結果的數據,我分別取了最具有代表性的的六組數據,分別如下:
[html]view plaincopy
第一組售票數據結果:
第一售票窗口售票數:43147
第二售票窗口售票數:5429
第三售票窗口售票數:1424
第二組售票數據結果:
第一售票窗口售票數:13290
第二售票窗口售票數:35528
第三售票窗口售票數:1182
第三組售票數據結果:
第一售票窗口售票數:45318
第二售票窗口售票數:3905
第三售票窗口售票數:777
第四組售票數據結果:
第一售票窗口售票數:16950
第二售票窗口售票數:32200
第三售票窗口售票數:850
第五組售票數據結果:
第一售票窗口售票數:44568
第二售票窗口售票數:3288
第三售票窗口售票數:2144
第六組售票數據結果:
第一售票窗口售票數:41628
第二售票窗口售票數:3928
第三售票窗口售票數:4444
觀察上面六組售票數據結果,我們可以發現,一般的線程優先級越高的線程,它所得到的執行的機會越多,那最終所出售的票數就越多,但這也不是嚴格按照這個規律來進行的。比如我們看一下第二和第四組數據,我們發現,雖然第一售票窗口的線程優先級比第二窗口的線程優先級更高,但第二窗口所出售的票比第一窗口要更多。同樣的,在看下第六組數據,比較第二窗口和第三窗口的售票數情況,也是一樣的現象。
至此,通過上面的實驗我們可知,在我們編寫多線程程序時,不能過度使用線程的優先級,特別是不能將構建正確的程序建立在依賴線程優先級的基礎之上,因為程序并不保證擁有較高優先級的線程一定會有更多的執行資源。況且線程的優先級嚴重依賴具體操作系統和虛擬機版本,在Java跨平臺開發大行其道的今天,若將使用過多優先級的Java程序部署在不同的系統平臺上,會產生一些意想不到的問題。所以,以后若要使用線程的優先級,要三思啊!!
小結:今天我們學習了一些線程的特性,主要包括:(1)線程的各種狀態;(2)線程的分類;(3)線程的優先級。我們著重講了一下線程的各種狀態的特點,并通過一張狀態圖清晰地了解了狀態之間的關系和切換條件。還有就是分析了線程的優先級方面的知識,Java中線程的優先級嚴重依賴具體的操作系統和虛擬機的版本,因此我們使用的時候需謹慎。