Java執行緒詳解(2)-建立與啟動
一、定義執行緒
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(Runnabletarget) Thread(Runnabletarget, String name) Thread(ThreadGroupgroup, Runnable target) Thread(ThreadGroupgroup, Runnable target, String name) Thread(ThreadGroupgroup, Runnable target, String name, long stackSize)
其中:
Runnable target:實現了Runnable介面的類的例項。
- Thread類也實現了Runnable介面,因此,從Thread類繼承的類的例項也可以作為target傳入這個構造方法。
- 直接實現Runnable介面類的例項。
- 執行緒池建立多執行緒。
String name:執行緒的名子。這個名子可以在建立Thread例項後通過Thread類的setName方法設定。預設執行緒名:Thread-N,N是執行緒建立的順序,是一個不重複的正整數。
ThreadGroup group:當前建立的執行緒所屬的執行緒組。如果不指定執行緒組,所有的執行緒都被加到一個預設的執行緒組中。
long stackSize:執行緒棧的大小,這個值一般是CPU頁面的整數倍。如x86的頁面大小是4KB.在x86平臺下,預設的執行緒棧大小是12KB。
三、啟動執行緒
線上程的Thread物件上呼叫start()方法,而不是run()或者別的方法。
在呼叫start()方法之前:執行緒處於新狀態中,新狀態指有一個Thread物件,但還沒有一個真正的執行緒。
在呼叫start()方法之後:發生了一系列複雜的事情——
啟動新的執行執行緒(具有新的呼叫棧);
該執行緒從新狀態轉移到可執行狀態;
當該執行緒獲得機會執行時,其目標run()方法將執行。
注意:對Java來說,run()方法沒有任何特別之處。像main()方法一樣,它只是新執行緒知道呼叫的方法名稱(和簽名)。因此,在Runnable上或者Thread上呼叫run方法是合法的。但並不啟動新的執行緒。
四、例子
1、實現Runnable介面的多執行緒例子
/**
* 實現Runnable介面的類
*/
public class RunnableImpl implements Runnable{
private Stringname;
public RunnableImpl(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
for(long k=0;k<100000000;k++);
System.out.println(name+":"+i);
}
}
}
/**
* 測試Runnable類實現的多執行緒程式
*/
public class TestRunnable {
public static void main(String[] args) {
RunnableImpl ri1=new RunnableImpl("李白");
RunnableImpl ri2=new RunnableImpl("屈原");
Thread t1=new Thread(ri1);
Thread t2=new Thread(ri2);
t1.start();
t2.start();
}
}
執行結果:
屈原:0
李白:0
屈原:1
李白:1
屈原:2
李白:2
李白:3
屈原:3
李白:4
屈原:4
2、擴充套件Thread類實現的多執行緒例子
/**
* 測試擴充套件Thread類實現的多執行緒程式
*/
public class TestThread extends Thread {
public TestThread(String name){
super(name);
}
@Override
public void run() {
for(int i=0;i<5;i++){
for(long k=0;k<100000000;k++);
System.out.println(this.getName()+":"+i);
}
}
public static void 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
對於上面的多執行緒程式程式碼來說,輸出的結果是不確定的。其中的一條語句for(long k=0;k<100000000;k++);是用來模擬一個非常耗時的操作的。
五、一些常見問題
1、執行緒的名字,一個執行中的執行緒總是有名字的,名字有兩個來源,一個是虛擬機器自己給的名字,一個是你自己的定的名字。在沒有指定執行緒名字的情況下,虛擬機器總會為執行緒指定名字,並且主執行緒的名字總是mian,非主執行緒的名字不確定。
2、執行緒都可以設定名字,也可以獲取執行緒的名字,連主執行緒也不例外。
3、獲取當前執行緒的物件的方法是:Thread.currentThread();
4、在上面的程式碼中,只能保證:每個執行緒都將啟動,每個執行緒都將執行直到完成。一系列執行緒以某種順序啟動並不意味著將按該順序執行。對於任何一組啟動的執行緒來說,排程程式不能保證其執行次序,持續時間也無法保證。
5、當執行緒目標run()方法結束時該執行緒完成。
6、一旦執行緒啟動,它就永遠不能再重新啟動。只有一個新的執行緒可以被啟動,並且只能一次。一個可執行的執行緒或死執行緒可以被重新啟動。
7、執行緒的排程是JVM的一部分,在一個CPU的機器上上,實際上一次只能執行一個執行緒。一次只有一個執行緒棧執行。JVM執行緒排程程式決定實際執行哪個處於可執行狀態的執行緒。
眾多可執行執行緒中的某一個會被選中做為當前執行緒。可執行執行緒被選擇執行的順序是沒有保障的。
8、儘管通常採用佇列形式,但這是沒有保障的。佇列形式是指當一個執行緒完成“一輪”時,它移到可執行佇列的尾部等待,直到它最終排隊到該佇列的前端為止,它才能被再次選中。事實上,我們把它稱為可執行池而不是一個可執行佇列,目的是幫助認識執行緒並不都是以某種有保障的順序排列而成一個一個佇列的事實。
9、儘管我們沒有無法控制執行緒排程程式,但可以通過別的方式來影響執行緒排程的方式。