1. 程式人生 > 實用技巧 >第11組 Alpha衝刺 (當前輪次3/6)

第11組 Alpha衝刺 (當前輪次3/6)

Java多執行緒1:三種實現方法。

分別是繼承Thread類、實現Runnable介面、實現Callable介面通過FutureTask包裝器來建立Thread執行緒。使用ExecutorService、Callable、Future實現有返回結果的多執行緒用到了執行緒池,後面再說。。

其中前兩種方式執行緒執行完後都沒有返回值,後兩種是帶返回值的。

原文連結 :https://www.cnblogs.com/felixzh/p/6036074.html

https://www.cnblogs.com/zivwong/p/9559931.html

1、繼承Thread類建立執行緒
Thread類本質上是實現了Runnable介面的一個例項,代表一個執行緒的例項。啟動執行緒的唯一方法就是通過Thread類的start()例項方法。start()方法是一個native方法,它將啟動一個新執行緒,並執行run()方法。這種方式實現多執行緒很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啟動新執行緒並執行自己定義的run()方法。例如:

定義新的類,繼承Thread,重寫run方法:

1 public class MyThread extends Thread {
2     @Override
3     public void run(){
4         super.run();
5         System.out.println("執行子執行緒...");
6     }
7 }
View Code

測試用例:

1 public class Test {
2     public static void main(String[] args) {
3         MyThread myThread = new
MyThread(); 4 myThread.start(); 5 System.out.println("主執行緒..."); 6 } 7 }
View Code

執行結果:

當然,這裡的結果不代表執行緒的執行順序,執行緒是併發執行的,如果多執行幾次,列印順序可能會不一樣。多執行緒的執行過程中,CPU是以不確定的方式去執行執行緒的,故執行結果與程式碼的執行順序或者呼叫順序無關,執行結果也可能不一樣。

這裡還有一個需要注意的點就是main方法中應該呼叫的是myThread的start方法,而不是run()方法。呼叫start()方法是告訴CPU此執行緒已經準備就緒可以執行,進而系統有時間就會來執行其run()方法。而直接呼叫run()方法,則不是非同步執行,而是等同於呼叫函式般按順序同步執行,這就失去了多執行緒的意義了。


2、實現Runnable介面建立執行緒
如果自己的類已經extends另一個類,就無法直接extends Thread,此時,可以實現一個Runnable介面,如下:

1 public class MyThread extends OtherClass implements Runnable {  
2   public void run() {  
3    System.out.println("MyThread.run()");  
4   }  
5 }  
View Code

舉例說明:

1 public class MyRunnable implements Runnable {
2     @Override
3     public void run() {
4         System.out.println("執行子執行緒...");
5     }
6 }
View Code

測試用例:

1 public class Test {
2     public static void main(String[] args) {
3 
4         Runnable runnable = new MyRunnable();
5         Thread thread = new Thread(runnable);
6         thread.start();
7         System.out.println("主執行緒執行結束!");
8     }
9 }
View Code

執行結果:

這裡main中可以看到真正建立新執行緒還是通過Thread建立:

Thread thread = new Thread(runnable);

這一步Thread類的作用就是把run()方法包裝成執行緒執行體,然後依然通過start去告訴系統這個執行緒已經準備好了可以安排執行。


3、使用Callable和Future建立執行緒

上面的兩種方式都有這兩個問題:

  1. 無法獲取子執行緒的返回值
  2. run方法不可以丟擲異常

為了解決這兩個問題,我們就需要用到Callable這個介面了。說到介面,上面的Runnable介面實現類例項是作為Thread類的建構函式的引數傳入的,之後通過Thread的start執行run方法中的內容。但是Callable並不是Runnable的子介面,是個全新的介面,它的例項不能直接傳入給Thread構造,所以需要另一個介面來轉換一下。

Java5提供了Future介面來代表Callable接口裡call()方法的返回值,併為Future介面提供了一個實現類FutureTask,該實現類的繼承關係如圖所示:

可以看到,該實現類不僅實現了Future介面,還實現了Runnable介面,所以可以直接傳給Thread建構函式。

而關於FutureTask的建構函式如下:

所以這裡面其實就是要比上一個方法再多一個轉換過程,最終一樣是通過Thread的start來建立新執行緒。有了這個思路,程式碼就很容易理解了:

import java.util.concurrent.Callable;

public class MyCallable implements Callable {
    int i = 0;
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"  i的值:"+ i);
        return i++; //call方法可以有返回值
    }
}

測試類:

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

public class Test {
    public static void main(String[] args) {
        Callable callable = new MyCallable();
        for (int i = 0; i < 10; i++) {
            FutureTask task = new FutureTask(callable);
            new Thread(task,"子執行緒"+ i).start();
            try {
                //獲取子執行緒的返回值
                System.out.println("子執行緒返回值:"+task.get() + "\n");
            }  catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

部分執行結果: