1. 程式人生 > >多執行緒(二)執行緒的建立和啟用

多執行緒(二)執行緒的建立和啟用

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介面的方式來建立多執行緒。