線程的介紹
概念:線程是程序的執(zhí)行路徑,每一個線程都有自己的局部變量久妆,程序計數(shù)器晌杰,以及各自的生命周期。
快速啟動一個線程
認識Thread Class類
public static void main(String [] args){
new Thread(){
@Override
public void run(){
enjoyMusic();
}
}.start();
enjoyNews();
public static void enjoyMusic(){
for(;;){
System.out.println("我在播放音樂");
}
}
public static void enjoyNews(){
for(;;){
System.out.println("我在看新聞呢!");
}
}
執(zhí)行結果
我在播放音樂和我在看新聞呢筷弦! 交替輸出
}
使用Jconsole觀察線程
可以使用JDK的Bin目錄下的Jconsole.exe 觀察當前線程的信息
線程的生命周期
問題:線程Thread執(zhí)行了start方法就表示該線程已近執(zhí)行了忙肋演?
下面看線程生命周期圖
由以上圖可以知道,線程的生命周期大致可以分為以下五個階段:
NEW
RUNNABLE
RUNNING
BLOCKED
TERMINATED
1.NEW狀態(tài)
當我們new出一個Thread類的時候,此時它并不在執(zhí)行狀態(tài)惋啃,并且它并沒有調用Start方法哼鬓,那么此時線程的狀態(tài)為New监右。
NEW狀態(tài)通過Start 方法進入RUNNABLE 狀態(tài)
2.RUNNABLE狀態(tài)
一個線程new出來之后边灭,通過調用start方法進入RUNNABLE 狀態(tài),此時線程正在會立即執(zhí)行忙健盒?答案是否定的绒瘦,線程和進程一樣 都要聽命于CPU調度(其實是在等待獲取CPU時間片),此時它只具備可執(zhí)行資格扣癣,但是并沒有真正的執(zhí)行惰帽,
并且線程存在RUNNING 狀態(tài) 不會一下就進入阻塞狀態(tài)(BLOCKED)狀態(tài)和消亡狀態(tài)(TERMINATED),即使是在線程運行期間執(zhí)行父虑,wait,sleep或者其他阻塞該線程的操作该酗,也必須獲得CPU調度的執(zhí)行權 才可以。RUNNABLE 的線程只能進入RUNNING狀態(tài)或者意外終止士嚎。
3.RUNNING 狀態(tài)
一旦CPU輪詢或者其他方式從任務可執(zhí)行隊列中選中了線程呜魄,才能真正的執(zhí)行自己的代碼,正在RUNNING 的線程其實也是RUNNABLE 的莱衩,則反過來不成立爵嗅,在該狀態(tài)中,線程可以發(fā)生如下的狀態(tài)改變笨蚁。
- 直接進入消亡狀態(tài)(TERMINATED),比如執(zhí)行了JDK已近不推薦的Stop方法睹晒。
- 進入阻塞狀態(tài)(BLOCKED) ,如調用了Sleep()方法括细,或者wait方法伪很。
- 進行了阻塞的IO操作,從而網(wǎng)絡數(shù)據(jù)的讀寫進入了BLOCKED 狀態(tài) 奋单。
- 獲取某個資源是掰,從而加入到改鎖的阻塞隊列 ,從而進入了 BLOCKED狀態(tài)辱匿。
- 由于CPU調度器輪詢使該線程放棄執(zhí)行键痛,進入BLOCKED狀態(tài)。
- 線程主動調用yield,放棄CPU執(zhí)行權匾七,進入BLOCKED狀態(tài)絮短。
4.BLOCKED狀態(tài)
BLOCKED狀態(tài)跟RUNNING狀態(tài)類似,可以發(fā)生如下的狀態(tài)改變 - 直接進入消亡狀態(tài)(TERMINATED),比如執(zhí)行了JDK已近不推薦的Stop方法昨忆。
- 線程阻塞的操作結束丁频,比如讀取到了想要讀取的數(shù)據(jù),進入RUNNABLED狀態(tài),
- 線程完成了休眠的狀態(tài)席里,進入到了RUNNABLED狀態(tài)
- Wait中的線程被其他線程喚醒(notify,notifyall)叔磷,進入到RUNNABLED狀態(tài)
- 線程獲取到了某個鎖資源,進入到了RUNNABLED狀態(tài)
- 線程在阻塞過程中奖磁,被打斷(調用Interrupt)改基,從而進入到了RUNNABLED狀態(tài),
5.TERMINATED 狀態(tài)
TERMINATED 是一個最終 的狀態(tài)咖为,該線程的狀態(tài)不會有TERMINATED 轉換到其他的狀態(tài)秕狰,進入到TERMINATED 狀態(tài),意味著該線程的聲明周期結束了躁染,以下操作鸣哀,會讓線程進入TERMINATED 狀態(tài) - 線程正常結束,進入TERMINATED狀態(tài)
- 線程運行出錯吞彤,意外終止
- JVM Crash 所有線程都結束
線程Start方法剖析
一個線程Thread new 出來之后我衬,重寫run()方法,并且 調用start方法饰恕,進入到了可執(zhí)行狀態(tài)(RUNNABLED)挠羔。那么run方法和start方法究竟是什么關系呢?看源碼
private volatile int threadStatus = 0;
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
以上代碼最關鍵的部分是調用了start0()這個方法懂盐,重寫Run方法何時被調用了呢褥赊?在開始一個線程的時候JVM會調用該線程的Run方法,run方法是被JNI方法start0()調用的莉恼,查看源碼我們發(fā)現(xiàn)以下問題:
1.當Thread被構造出new狀態(tài)后拌喉,他的threadStatus 默認為0
2.不能兩次啟動,兩次調用start方法,否則機會拋出IllegalThreadStateException 異常
3.線程啟動會被加入ThreadGroup中俐银,
4.一個線程的生命周期結束尿背,也就是到了TERMINATED 狀態(tài),再次start方法捶惜,是不被允許的田藐,也就是說TERMINATED 狀態(tài)沒有辦法回到,RUNNABLED/RUNNING狀態(tài)的
Thread t = new Thread(new Runnable() {
@Override
public void run() {
sleep(10);
}
});
t.start();
t.start();
上述代碼會拋出 IllegalThreadStateException 異常吱七,我們可以改動一下模擬線程的聲明周期結束汽久。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
sleep(1);
}
});
t.start();
TimeUnit.SECONDS.sleep(2);//休眠是確保thread結束聲明周期
t.start();//重新激活線程
以上兩個例子都會拋出異常 ,但是意義是不一樣的踊餐,第一個線程不能被啟動兩次景醇,第二個啟動是不被允許的。
模板模式在Thread中的應用
通過上述代碼吝岭,我們不難看出三痰,線程執(zhí)行的業(yè)務邏輯在Run方法內部吧寺,我們用重寫run方法 用start方法啟動線程,其實Thread中的run方法和start是一個比較典型的模板設計模式散劫,父類定義算法結構代碼稚机,子類實現(xiàn)業(yè)務邏輯。
下面舉個列子
銀行有四臺取號機获搏,每臺取號機都有排隊等候赖条,當你進入業(yè)務大廳的時候需要取號,領取一張紙質號碼颜凯,假設有四臺出號機器谋币, 每天最對受理30筆業(yè)務仗扬,ticketShow 代表出號機器
public class ticketShow extends Thread{
public final String name;
public static final int MAX = 50;
private int index = 1;
ticketShow (String name){
this.name = name;
}
@Overried
public void run(){
while(index <= MAX){
System.out.println("當前機器:"+name+",出的號碼是:"+(index++));
}
}
}
接下來編寫一個main方法
public static void main(String [] args){
ticketShow ts = new ticketShow ("一號機器");
ts.start();
ticketShow ts = new ticketShow ("二號機器");
ts.start();
ticketShow ts = new ticketShow ("三號機器");
ts.start();
ticketShow ts = new ticketShow ("四號機器");
ts.start();
}
然而每臺機器都是從1到50症概,四個線程并沒有交互,獲取唯一一個遞增的號碼早芭,那么我們改進一下
public class ticketShow extends Thread{
public final String name;
public static final int MAX = 50;
private static int index = 1;
ticketShow (String name){
this.name = name;
}
@Overried
public void run(){
while(index <= MAX){
System.out.println("當前機器:"+name+",出的號碼是:"+(index++));
}
}
}
再次運行彼城,發(fā)現(xiàn)四個出號機器,交替輸出不同的號碼退个,
通過對Index進行static 修飾募壕,做到了多線程下共享資源的唯一性,看起來似乎滿足了我們的需求语盈,但是還是有些問題 比如我們的共享資源很多呢舱馅,要經(jīng)過一些復雜的計算呢?等等等刀荒。代嗤。。
策略模式在Runnable在thread中的應用
很多文章中都會說缠借,創(chuàng)建線程有兩種方式干毅,第一個是構造Thread,第二個是構造Runnable 其實這種說法是錯誤的泼返,最起碼是不嚴謹?shù)南醴辏瑴蚀_來說,創(chuàng)建線程只有一種方式绅喉,那就是構造Thread類渠鸽,而實現(xiàn)線程的執(zhí)行單元有兩種方式,第一個是重寫Thread中的run方法柴罐,第二個是重寫runnable中的run方法徽缚,當然他們還是有些不同的,那就是Thread類中的run方法是不能共享的丽蝎,也就是說線程A不能把線程B中的run方法當做自己的執(zhí)行單元猎拨,然而runnable接口就很容易實現(xiàn)這一點膀藐。使用不同的runable構造不同的thread實例。
那我們對銀行取票機代碼進行 重構一下
public class ticketShow implements runnable{
public final String name;
public static final int MAX = 50;
private int index = 1; //不做static 修飾
ticketShow (String name){
this.name = name;
}
@Overried
public void run(){
while(index <= MAX){
System.out.println("當前機器:"+name+",出的號碼是:"+(index++));
sleep(100);
}
}
}
添加main方法
public static void main(String [] args){
final ticketShow ts = new ticketShow ();
Thread t1 = new Thread(ts,"一號機器");
t1.start();
Thread t2 = new Thread(ts,"二號機器");
t2.start();
Thread t3 = new Thread(ts,"三號機器");
t3.start();
Thread t4 = new Thread(ts,"四號機器");
t4.start();
}
可以看到上述代碼的輸入和繼承Thread類實現(xiàn)并且是同樣的效果红省,四個較好機器線程都實現(xiàn)了同一個接口Runnable额各,這樣她們的資源都是共享的,不會出現(xiàn)每一個叫號機器從一到五十的情況吧恃。