使用java.lang.Thread類或者java.lang.Runnable介面編寫代碼來定義、執行個體化和啟動新線程。
一個Thread類執行個體只是一個對象,像Java中的任何其他對象一樣,具有變數和方法,生死於堆上。
Java中,每個線程都有一個調用棧,即使不在程式中建立任何新的線程,線程也在後台運行著。
一個Java應用總是從main()方法開始運行,mian()方法運行在一個線程內,它被稱為主線程。
一旦建立一個新的線程,就產生一個新的調用棧。
線程總體分兩類:使用者線程和守候線程。
當所有使用者線程執行完畢的時候,JVM自動關閉。但是守候線程卻不獨立於JVM,守候線程一般是由作業系統或者使用者自己建立的 Java線程:建立與啟動
一、定義線程
1、擴充java.lang.Thread類。
此類中有個run()方法,應該注意其用法:
public void run()
如果該線程是使用獨立的Runnable運行物件建構的,則調用該Runnable對象的run方法;否則,該方法不執行任何操作並返回。
Thread的子類應該重寫該方法。
2、實現java.lang.Runnable介面。
void run()
使用實現介面Runnable的對象建立一個線程時,啟動該線程將導致在獨立執行的線程中調用對象的run方法。
方法run的常規協定是,它可能執行任何所需的操作。
二、執行個體化線程
1、如果是擴充java.lang.Thread類的線程,則直接new即可。
2、如果是實現了java.lang.Runnable介面的類,則用Thread的構造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
三、啟動線程
線上程的Thread對象上調用start()方法,而不是run()或者別的方法。
在調用start()方法之前:線程處於新狀態中,新狀態指有一個Thread對象,但還沒有一個真正的線程。
在調用start()方法之後:發生了一系列複雜的事情
啟動新的執行線程(具有新的調用棧);
該線程從新狀態轉移到可運行狀態;
當該線程獲得機會執行時,其目標run()方法將運行。
注意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新線程知道調用的方法名稱(和簽名)。因此,在Runnable上或者Thread上調用run方法是合法的。但並不啟動新的線程。
四、例子
1、實現Runnable介面的多線程例子
/**
* 實現Runnable介面的類
*
* @author leizhimin 2008-9-13 18:12:10
*/
publicclass DoSomethingimplements Runnable {
private String name;
public DoSomething(String name) {
this.name = name;
}
publicvoid run() {
for (int i = 0; i < 5; i++) {
for (long k = 0; k < 100000000; k++) ;
System.out.println(name + ": " + i);
}
}
}
/**
* 測試Runnable類實現的多線程程式
*
* @author leizhimin 2008-9-13 18:15:02
*/
publicclass TestRunnable {
publicstaticvoid main(String[] args) {
DoSomething ds1 = new DoSomething("阿三");
DoSomething ds2 = new DoSomething("李四");
Thread t1 = new Thread(ds1);
Thread t2 = new Thread(ds2);
t1.start();
t2.start();
}
}
執行結果:
李四: 0
阿三: 0
李四: 1
阿三: 1
李四: 2
李四: 3
阿三: 2
李四: 4
阿三: 3
阿三: 4
Process finished with exit code 0
2、擴充Thread類實現的多線程例子
/**
* 測試擴充Thread類實現的多線程程式
*
* @author leizhimin 2008-9-13 18:22:13
*/
publicclass TestThreadextends Thread{
public TestThread(String name) {
super(name);
}
publicvoid run() {
for(int i = 0;i<5;i++){
for(long k= 0; k <100000000;k++);
System.out.println(this.getName()+" :"+i);
}
}
publicstaticvoid main(String[] args) {
Thread t1 = new TestThread("阿三");
Thread t2 = new TestThread("李四");
t1.start();
t2.start();
}
}
執行結果:
阿三 :0
李四 :0
阿三 :1
李四 :1
阿三 :2
李四 :2
阿三 :3
阿三 :4
李四 :3
李四 :4
Process finished with exit code 0
對於上面的多線程程式碼來說,輸出的結果是不確定的。其中的一條語句for(long k= 0; k <100000000;k++);是用來類比一個非常耗時的操作的。
五、一些常見問題
1、線程的名字,一個運行中的線程總是有名字的,名字有兩個來源,一個是虛擬機器自己給的名字,一個是你自己的定的名字。在沒有指定線程名字的情況下,虛擬機器總會為線程指定名字,並且主線程的名字總是mian,非主線程的名字不確定。
2、線程都可以設定名字,也可以擷取線程的名字,連主線程也不例外。
3、擷取當前線程的對象的方法是:Thread.currentThread();
4、在上面的代碼中,只能保證:每個線程都將啟動,每個線程都將運行直到完成。一系列線程以某種順序啟動並不意味著將按該順序執行。對於任何一組啟動的線程來說,發送器不能保證其執行次序,期間也無法保證。
5、當線程目標run()方法結束時該線程完成。
6、一旦線程啟動,它就永遠不能再重新啟動。只有一個新的線程可以被啟動,並且只能一次。一個可啟動並執行線程或死線程可以被重新啟動。
7、線程的調度是JVM的一部分,在一個CPU的機器上上,實際上一次只能運行一個線程。一次只有一個線程棧執行。JVM線程發送器決定實際運行哪個處於可運行狀態的線程。
眾多可運行線程中的某一個會被選中做為當前線程。可運行線程被選擇啟動並執行順序是沒有保障的。
8、儘管通常採用隊列形式,但這是沒有保障的。隊列形式是指當一個線程完成“一輪”時,它移到可運行隊列的尾部等待,直到它最終排隊到該隊列的前端為止,它才能被再次選中。事實上,我們把它稱為可運行池而不是一個可運行隊列,目的是協助認識線程並不都是以某種有保障的順序排列唱呢個一個隊列的事實。
9、儘管我們沒有無法控制線程發送器,但可以通過別的方式來影響線程調度的方式。 Java線程:線程棧模型與線程的變數
要理解線程調度的原理,以及線程執行過程,必須理解線程棧模型。
線程棧是指某時刻時記憶體中線程調度的棧資訊,當前調用的方法總是位於棧頂。線程棧的內容是隨著程式的運行動態變化的,因此研究線程棧必須選擇一個啟動並執行時刻(實際上指代碼運行到什麼地方)。
下面通過一個樣本性的代碼說明線程(調用)棧的變化過程。
這幅圖描述在代碼執行到兩個不同時刻1、2時候,虛擬機器線程調用棧示意圖。
當程式執行到t.start();時候,程式多出一個分支(增加了一個調用棧B),這樣,棧A、棧B並存執行。
從這裡就可以看出方法調用和線程啟動的區別了。 Java線程:線程狀態的轉換
一、線程狀態
線程的狀態轉換是線程式控制制的基礎。線程狀態總的可分為五大狀態:分別是生、死、可運行、運行、等待/阻塞。用一個圖來描述如下:
1、新狀態:線程對象已經建立,還沒有在其上調用start()方法。
2、可運行狀態:當線程有資格運行,但發送器還沒有把它選定為運行線程時線程所處的狀態。當start()方法調用時,線程首先進入可運行狀態。線上程運行之後或者從阻塞、等待或睡眠狀態回來後,也返回到可運行狀態。
3、運行狀態:線程發送器從可運行池中選擇一個線程作為當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一一種方式。
4、等待/阻塞/睡眠狀態:這是線程有資格運行時它所處的狀態。實際上這個三狀態組合為一種,其共同點是:線程仍舊是活的,但是當前沒有條件運行。換句話說,它是可啟動並執行,但是如果某件事件出現,他可能返回到可運行狀態。
5、死亡態:當線程的run()方法完成時就認為它死去。這個線程對象也許是活的,但是,它已經不是一個單獨執行的線程。線程一旦死亡,就不能複生。如果在一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。
有關詳細狀態轉換圖可以參看本人的“Java多線程編程總結”中的圖
二、阻止線程執行
對於線程的阻止,考慮一下三個方面,不考慮IO阻塞的情況:
睡眠;
等待;
因為需要一個對象的鎖定而被阻塞。
1、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的線程休眠(暫停執行),以“減慢線程”。當線程睡眠時,它入睡在某個地方,在蘇醒之前不會返回到可運行狀態。當睡眠時間到期,則返回到可運行狀態。
線程睡眠的原因:線程執行太快,或者需要強制進入下一輪,因為Java規範不保證合理的輪換。
睡眠的實現:調用靜態方法。
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置:為了讓其他線程有機會執行,可以將Thread.sleep()的調用放線程run()之內。這樣才能保證該線程執行過程中會睡眠。
例如,在前面的例子中,將一個耗時的操作改為睡眠,以減慢線程的執行。可以這麼寫:
public void run() {
for(int i = 0;i<5;i++){
// 很耗時的操作,用來減慢線程的執行
// for(long k= 0; k <100000000;k++);
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace(); .
}
System.out.println(this.getName()+" :"+i);
}
}