1. 程式人生 > >多執行緒學習(一)——建立執行緒的三種方式及比較

多執行緒學習(一)——建立執行緒的三種方式及比較

最近在學習多執行緒,在這裡總結一下學習到的內容(參考《瘋狂Java講義第3版》):

一、建立執行緒有三種方式:

      1、繼承Thread類

      2、實現Runnable介面

      3、使用Callable和Future

二、分別介紹用法:

繼承Thread類

/*下面的例子通過執行結果會看到,有3個執行緒,兩個子執行緒,一個主執行緒
 * java執行時預設的主執行緒就是main()方法,其中main()方法的方法體就是主執行緒的執行緒執行體
 * 執行結果可以看到兩個子執行緒輸出的i變數不連續,注意:i變數是FirstThread的例項變數,而不是區域性變數
 * 但是因為程式每次建立執行緒物件時都需要建立一個FirstThread物件,所以兩個執行緒不能共享該示例變數
 * 使用繼承Thread類的方法來建立執行緒類時,多個執行緒之間無法共享執行緒類的例項變數。
 * */
public class FirstThread extends Thread{
	
	private int i;
	//重寫run()方法,run()方法的方法執行體就是執行緒執行體
	public void run() {
     for(;i<100;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 FirstThread().start();
				//建立並啟動第二個執行緒
				new FirstThread().start();
			}			
		}
	}

}

執行結果:


實現Runnable介面

/**
 * 通過執行結果可以看到,兩個子執行緒的i變數有時候是連續的
 * 採用Runnable介面方式建立的多個執行緒可以共享執行緒類的例項變數。這是因為在這種方式下,
 * 執行緒所建立的Runnable物件只是執行緒的target,而多個執行緒可以共享同一個target,
 * 所以多個執行緒可以共享同一個執行緒類(實際上是同一個執行緒target類)的例項變數
 * **/
public class RunnableThread implements Runnable{
	
	private int i;
    
	//run()方法同樣是執行緒執行體
	@Override
	public void run() {
		for(;i<100;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) {
				RunnableThread runna = new RunnableThread();
				//通過new Thread(target,name)方法建立新執行緒
				new Thread(runna,"新執行緒1").start();
				new Thread(runna,"新執行緒2").start();
			}
		}	
	}

}

執行結果:


使用Callable和Future

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * 通過實現Runnable介面建立多執行緒時,Thread類的作用就是把run()方法包裝成執行緒執行體
 * 那麼是否可以把任意方法都包裝成執行緒執行體呢?java目前不行,但是java模仿者c#可以
 * (c#可以把任意的方法包裝成執行緒執行體,包括有返回值的方法)
 * 受上面的啟發,從java5開始,java提供了Callable介面,它如同Runnable介面的增強版
 * Callable介面提供了一個call()方法可以作為執行緒執行體,但是它比run()方法的功能更加強大
 * call()方法可以有返回值
 * call()方法可以宣告丟擲異常
 * 函式式介面可以使用Lambda表示式
 * (函式式介面:只包含一個方法的抽象介面,函式式介面可以包含多個預設方法,類方法,但只能宣告一個抽象方法)
 * **/
public class CallableThread {
	
	public static void main(String[] args) {
		//建立Callable物件
		CallableThread callable = new CallableThread();
		//先試用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();
		}		
	}

}

執行結果(擷取部分結果):



我在編寫上面Lambda表示式那塊,剛開始始終有報錯,顯示jdk版本需要在1.8以上,重新安裝了jdk1.8,修改了程式執行jdk版本,如下所示:

1、安裝並修改了程式引用jdk


2、修改了eclipse的啟動jdk,在eclipse的安裝目錄下的eclipse.ini檔案,添加了如下紅框中內容:


修改後,依然報上面的錯誤,最後發現,需要修改eclipse中的編譯環境,改為1.8:


這樣,問題就解決了。

三、建立執行緒的三種方式比較

        通過繼承Thread類或者實現Runnable、Callable介面都可以實現多執行緒,不過實現Runnable介面與實現Callable介面的方式基本相同,只是Callable接口裡面定義的方法有返回值,可以宣告丟擲異常。因此可以將實現Runnable、Callable介面歸為一種方式。這種方式與繼承Thread方式之間的主要差別如下:

        採用實現Runnable、Callable介面的方式建立多執行緒的優缺點

       1、執行緒類只是實現了Runnable介面或Callable介面,還可以繼承其他父類

       2、在這種方式下,多個執行緒可以共享同一個target物件,所以適合多個相同執行緒來處理同一份資源的情況,從而可以將CPU、程式碼和資料分開,形成清晰的模型,較好的體現了面向物件的思想

       3、劣勢是,程式設計稍微複雜,如果需要訪問當前執行緒,則必須使用Thread.currentThread()方法

       採用繼承Thread類的方式建立多執行緒的優缺點

       1、劣勢是,因為執行緒類已經繼承了Thread類,所以不能在繼承其他的類

       2、優勢是,編寫簡單,如果需要訪問當前執行緒,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前執行緒。

     鑑於以上分析,一般推薦採用實現Runnable介面或Callable介面的方式來建立多執行緒