Java 多線程(1):得到線程的返回結果

Java 對多線程編程提供了內置的支持並提供了良好的 API,經過使用 ThreadRunnable 兩個基礎類,咱們能夠很方便的建立一個線程:java

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("線程啓動");
        // 耗時操做
        System.out.println("線程結束");
    }
};

Thread thread = new Thread(runnable); // 建立線程,runnable 做爲線程要執行的任務(載體)
thread.start(); // 啓動線程
thread.join(); // 等待線程執行完畢

{ 題外話開始:編程

經過 Thread 的類聲明:
Thread 的類聲明segmentfault

咱們能夠知道 Thread 本身也實現了 Runnable 接口,Threadrun 方法的實現以下(Thread 啓動以後運行的就是 Thread 中的 run 方法):
Thread 中 run 方法的實現多線程

target 即構造 Thread 時可傳入的 Runnable 對象,不傳入即爲 null —— 因此繼承 Thread 重寫其 run 方法也是一種建立線程的方式)ide

題外話結束 }this


Runnable.java 的代碼:
Runnable.java 的代碼spa

Runnablerun 方法是不帶返回值的,那若是咱們須要一個耗時任務在執行完以後給予返回值,應該怎麼作呢?線程

第一種方法:在 Runnable 的實現類中設置一個變量 V,在 run 方法中將其改變爲咱們期待的結果,而後經過一個 getV() 方法將這個變量返回。3d

import java.util.*;

public class RunnableTest {

    public static void main(String[] args) throws Exception {
        System.out.println("使用 Runnable 得到返回結果:");

        List<Thread> workers = new ArrayList<>(10);
        List<AccumRunnable> tasks = new ArrayList<>(10);

        // 新建 10 個線程,每一個線程分別負責累加 1~10, 11~20, ..., 91~100
        for (int i = 0; i < 10; i++) {
            AccumRunnable task = new AccumRunnable(i * 10 + 1, (i + 1) * 10);
            Thread worker = new Thread(task, "慢速累加器線程" + i);

            tasks.add(task);
            workers.add(worker);

            worker.start();
        }

        int total = 0;
        for (int i = 0, s = workers.size(); i < s; i++) {
            workers.get(i).join();  // 等待線程執行完畢
            total += tasks.get(i).getResult();
        }

        System.out.println("\n累加的結果: " + total);
    }

    static final class AccumRunnable implements Runnable {

        private final int begin;
        private final int end;

        private int result;

        public AccumRunnable(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }

        @Override
        public void run() {
            result = 0;
            try {
                for (int i = begin; i <= end; i++) {
                    result += i;
                    Thread.sleep(100);
                }
            } catch (InterruptedException ex) {
                ex.printStackTrace(System.err);
            }
            System.out.printf("(%s) - 運行結束,結果爲 %d\n",
                    Thread.currentThread().getName(), result);
        }

        public int getResult() {
            return result;
        }
    }

}

運行結果:
使用 Runnable 得到返回結果code


第二種方法:使用 Callable<V>FutureTask<V>
Callable<V> 是 JDK1.5 時添加的類,爲的就是解決 Runnable 的痛點(沒有返回值不能拋出異常)。

Callable.java 的代碼:
Callable.java 的代碼

能夠看到參數化類型 V 就是返回的值的類型。


可是查看 Thread 的構造方法,咱們發現 Thread 只提供了將 Runnable 做爲參數的構造方法,並無使用 Callable<V> 的構造方法 —— 因此引出 FutureTask<V>

FutureTask<V> 也是 JDK1.5 時添加的類,查看它的類聲明:
FutureTask 的類聲明

能夠看到它實現了 RunnableFuture<V> 這個接口,咱們再看看 RunnableFuture<V>
RunnableFuture 的接口聲明

能夠看到 RunnableFuture 接口繼承了 Runnable 接口,那麼 RunnableFuture 接口的實現類 FutureTask 必然會去實現 Runnable 接口 —— 因此 FutureTask 能夠用來當 Runnable 使用。查看 FutureTask 的構造方法,發現 FutureTask 有兩個構造方法:
FutureTask 的兩個構造方法

第一個構造方法代表咱們能夠經過一個 Callable 去構造一個 FutureTask —— 而 FutureTask 實現了 Runnable 接口,從而能夠將該任務傳遞給 Thread 去運行;

第二個構造方法是經過一個 Runnable 和一個指定的 result 去構造 FutureTask


咱們再來看看 FutureTask<V> 經過 RunnableFuture<V> 實現的第二個接口:Future<V>
(事實上,RunnableFuture<V> 是在 JDK1.6 時添加的類,我猜想在 JDK1.5 時 FutureTask<V> 應該是直接實現的 RunnableFuture<V>,而不是經過 RunnableFuture<V>

Future.java 的源碼:
Future.java 的源碼

Future<V> 包含的方法有 5 個,本文只關注它兩個 get 相關的方法。經過 Java 的 API 文檔,咱們能夠知道 get 方法是用來返回和 Future 關聯的任務的結果(即構造 FutureTask<V> 時使用的 Callable<V> 的結果)。帶參數的 get 方法指定一個超時時間,在超時時間內該方法會阻塞當前線程,直到得到結果以後中止阻塞繼續運行 —— 若是在給定的超時時間內沒有得到結果,那麼便拋出 TimeoutException 異常;不帶參數的 get 能夠理解爲超時時間無限大,即一直等待直到得到結果。

Future 每一個方法的詳細用法能夠參考 Java 多線程(3)

經過以上咱們能夠知道,Callable<V>Future<V>FutureTask<V> 這三個類是相輔相成的。以上提到關鍵類的類關係以下:

以上幾個關鍵類的關係

如今咱們看看 FutureTask 中實現 Runnablerun 方法是怎樣的(咱們直接看關鍵代碼):

FutureTask 實現的 run 方法的關鍵代碼

代碼的意思很明確,調用構造 FutureTask<V> 時傳入的 Callable<V>call 方法,若是正常執行完畢,那麼經過 set(result) 設置結果,經過 get() 方法獲得的即爲這個結果 —— 思路和前面第一種方法是一致的(固然 FutureTask<V> 是更強大和更通用的類);不然即爲拋出異常的狀況。


使用 FutureTask<V> 的過程以下:
(1)經過一個 Callable<V> 任務或者一個 Runnable(一開始就指定 result)任務構造 FutureTask<V>
(2)將 FutureTask<V> 交給 Thread 去運行;
(3)使用 FutureTask<V>get 方法(或者 Threadjoin 方法)阻塞當前線程直到得到任務的結果。


如今咱們使用 Callable 改寫程序:

import java.util.*;
import java.util.concurrent.*;

public class CallableTest {

    public static void main(String[] args) throws Exception {
        System.out.println("使用 Callable 得到返回結果:");
        
        List<FutureTask<Integer>> futureTasks = new ArrayList<>(10);
        // 新建 10 個線程,每一個線程分別負責累加 1~10, 11~20, ..., 91~100
        for (int i = 0; i < 10; i++) {
            AccumCallable task = new AccumCallable(i * 10 + 1, (i + 1) * 10);
            FutureTask<Integer> futureTask = new FutureTask<>(task);
            futureTasks.add(futureTask);
            
            Thread worker = new Thread(futureTask, "慢速累加器線程" + i);
            worker.start();
        }

        int total = 0;
        for (FutureTask<Integer> futureTask : futureTasks) {
            total += futureTask.get(); // get() 方法會阻塞直到得到結果
        }

        System.out.println("累加的結果: " + total);
    }

    static final class AccumCallable implements Callable<Integer> {

        private final int begin;
        private final int end;

        public AccumCallable(int begin, int end) {
            this.begin = begin;
            this.end = end;
        }

        @Override
        public Integer call() throws Exception {
            int result = 0;
            for (int i = begin; i <= end; i++) {
                result += i;
                Thread.sleep(100);
            }
            System.out.printf("(%s) - 運行結束,結果爲 %d\n",
                    Thread.currentThread().getName(), result);

            return result;
        }
     
    }
    
}

運行結果:
使用 Callable得到返回結果

能夠看到使用 Callable<V> + FutureTask<V> 的程序代碼要比 Runnable 的代碼更簡潔和方便 —— 當須要線程執行完成返回結果時(或者任務須要拋出異常),Callable<V> 是優先於 Runnable 的選擇。

相關文章
相關標籤/搜索