多執行緒(二)執行緒的建立和啟用
Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項。每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流(一段順序執行的程式碼)。Java使用執行緒執行體來代表這段程式流。
一、繼承Thread類建立執行緒類
package gblw.fisrt; //通過繼承Thread類來建立執行緒類 public class FisrtThread extends Thread{ private int i; //重寫run()方法,run()方法的方法體就是執行緒執行體 @Override public void run(){ for(;i<Integer.MAX_VALUE;i++){ //當執行緒類繼承Thread類時,直接使用this即可獲取當前執行緒 //Thread物件的getName()返回當前執行緒的名字 //因此可以直接呼叫getName()方法返回當前執行緒的名字 System.out.println(getName()+" "+i); } } public static void main(String[] args){ for(int i=0;i<100;i++){ //呼叫Thread的currentThread()方法獲取當前執行緒 System.out.println(Thread.currentThread().getName()+" "+i); if(i==20){ //建立並啟動第一個執行緒 new FisrtThread().start(); //建立並啟動第二個執行緒 new FisrtThread().start(); } } } }
二、實現Runnable介面建立執行緒類
package gblw.fisrt; //通過實現Runnable介面來建立執行緒類 public class SecondThread implements Runnable{ private int i; //run()方法同樣是執行緒執行體 @Override public void run() { for(;i<Integer.MAX_VALUE;i++){ //當執行緒類實現Runnable介面時 //如果想獲取當前執行緒,只能用Thread.currentThread()方法 System.out.println(Thread.currentThread().getName()+" "+i); } } public static void main(String[] args){ for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+" "+i); if(i==20){ SecondThread st=new SecondThread(); //通過new Thread(target,name)方法建立新執行緒 new Thread(st,"新執行緒1").start(); new Thread(st,"新執行緒2").start(); } } } }
說明:Runnable介面中只包含一個抽象方法,從Java 8開始,Runnable介面使用了@FunctionalInterface修飾。也就是說,Runnable介面是函式式介面,可使用Lambda表示式建立Runnable物件。接下來介紹的Callable也是函式式介面。
程式所建立的Runnable物件只是執行緒的target,而多個執行緒可以共享同一個target,所以多個執行緒可以共享同一個執行緒類(實際上應該是執行緒的target類)的例項變數。
三、使用Callable和Future建立執行緒
從Java 5開始,Java提供了Callable介面,Callable介面提供了一個call()方法可以作為執行緒執行體,但call()方法比run()方法功能更強大。
A、call()方法可以有返回值。
B、call()方法可以宣告丟擲異常。
上面說到call()方法可以有返回值,那麼如何取得call()方法的返回值呢?
Java 5提供了Future介面來代表Callable接口裡call()的返回值,併為Future介面提供了一個FutureTask實現類,該類裡定義瞭如下幾個公共方法來控制它關聯的Callable任務。
A、boolean cancel(boolean mayInterruptIfRunning):試圖取消該Future裡關聯的Callable任務。
B、V get():返回Callable任務裡call()方法的返回值。呼叫該方法將導致程式阻塞,必須等到子執行緒結束後才會得到返回值。
C、V get(long timeout,TimeUnit unit):返回Callable任務裡call()方法的返回值。該方法讓程式最多阻塞timeout和unit指定的時間,如果經過指定時間後Callable任務依然沒有返回值,將會丟擲TimeoutException異常。
D、boolean isCancelled():如果在Callable任務正常完成前被取消,則返回true。
E、boolean isDone():如果Callable任務已完成,則返回true。
說明:Callable介面有泛型限制,Callable接口裡的泛型形參型別與call()方法返回值型別相同。而且Callable介面是函式式介面,因此可使用Lambda表示式建立Callable物件。
package gblw.fisrt;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
//使用Callable介面來建立執行緒
public class ThirdThread {
public static void main(String[] args) {
//先使用Lambda表示式建立Callable<Integer>物件
//使用FutureTask來包裝Callable物件
FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
int i=0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值: "+i);
}
//call()方法可以有返回值
return i;
});
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值: "+i);
if(i==20){
//實質還是以Callable物件來建立並啟動執行緒的
new Thread(task,"有返回值的執行緒").start();
}
}
try {
System.out.println("子執行緒的返回值:"+task.get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("===========");
}
}
另一種寫法
package gblw.fisrt;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
//使用Callable介面來建立執行緒
public class FourThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int i=0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值: "+i);
}
//call()方法可以有返回值
return i;
}
public static void main(String[] args) {
FourThread rt=new FourThread();
FutureTask<Integer> task=new FutureTask<>(rt);
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"的迴圈變數i的值: "+i);
if(i==20){
//實質還是以Callable物件來建立並啟動執行緒的
new Thread(task,"有返回值的執行緒").start();
}
}
try {
System.out.println("子執行緒的返回值:"+task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
建立執行緒的三種方法對比
通過繼承Thread類或者實現Runnable、Callable介面都可以實現多執行緒,不過實現Runnable介面與實現Callable介面方式基本相同,只是Callable接口裡定義的方法有返回值,可以宣告丟擲異常而已。因此可以將實現Runnable介面和實現Callable介面歸為一種方式。這種方式與繼承Thread方式之間的主要差別如下。
採用實現Runnable、Callable介面的方式建立多執行緒的優缺點:
A、執行緒類只是實現了Runnable介面或Callable介面,還可以繼承其他類。
B、在這種方式下,多個執行緒可以共享同一個target物件,所以非常適合多個相同執行緒來處理同一分資源的情況,從而可以將CPU、程式碼和資料分開,形成清晰的模型,較好地體現了面向物件的思想。
C、劣勢是,程式設計稍稍複雜,如果需要訪問當前執行緒,則必須使用Thread.currentThread()方法。
採用繼承Thread類的方式建立多執行緒的優缺點:
A、劣勢是,因為執行緒類已經繼承了Thread類,所以不能再繼承其他父類。
B、優勢是,編寫簡單,如果需要訪問當前執行緒,則無須使用Thread.currentThread()方法,直接使用this即可獲得當前執行緒。
鑑於上面分析,因此一般推薦採用實現Runnable介面、Callable介面的方式來建立多執行緒。