java併發-2 Callable和Future-多線程的返回值

上面介紹了多線程啓動的兩種方式,無論繼承Thread還是實現Runnable接口,其內部的run方法都是void,也就是拿不到多線程的執行結果。

考慮下面一個小例子:有一個任務需要計算1-100的和,現代計算機肯定單線程分分鐘就計算完成了,加一點限制條件,每執行一次加法操作,cpu都會暫停1秒,現在還覺得單線程計算很快麼?現在就需要多線程來計算,比如5個線程同時計算,那麼理論上不考慮其他因素,只需要20m即可計算完成。

此時不能繼承Thread和實現Runnable接口來實現,因此計算後的返回值獲取不到,這時Callable和Future就登場了,可以採用兩種方式來實現:

1、使用ExecuteService線程池配置Callable和Future接口來實現,在執行Callable任務後,可以獲取一個Future對象,調用其get方法,就可以獲取到Callable任務的執行結果;Future是一個接口,提供了三種功能:

  • 判斷任務是否完成
  • 中斷任務
  • 獲取任務結果

這裏我們只關心任務結果,即get方法,代碼如下(簡便起見,停頓時間爲100ms,線程數爲2):

public class TestCallable {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();

        ExecutorService pool = Executors.newCachedThreadPool();
        CalculateTask task1 = new CalculateTask(1,50);
        Future<Integer> future1 = pool.submit(task1);
        CalculateTask task2 = new CalculateTask(50,101);
        Future<Integer> future2 = pool.submit(task2);
        pool.shutdown();

        System.out.println("主線程在執行任務");

        //這個get方法會阻塞,直到線程結束後返回結果
        try {
            int r1 = future1.get();
            int r2 = future2.get();
            int result = r1+r2;
            System.out.println("多線程執行結果:" + result);
            long end = System.currentTimeMillis();
            System.out.println("程序執行時間:" + (end-startTime)/1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 通過實現帶泛型的Callable接口就可以將執行後的返回值返回給上層
 * 上層通過ExecutorService.submit執行後就能獲得Future對象,
 * 該對象的get方法就能獲取多線程執行後的泛型值
 */
class CalculateTask implements Callable<Integer> {

    private int start;
    private int end;

    public CalculateTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    public Integer call() throws Exception {
        System.out.println("線程" + Thread.currentThread().getName() + " 開啓");
        int sum = 0;
        for(int i=start; i<end;i++) {
            sum += i;
            //1秒太長了,500吧
            Thread.sleep(100);
        }
        return sum;
    }
}

注意,get方法是一個阻塞方法,會等到子線程執行完成,纔會繼續執行,執行結果:

線程pool-1-thread-1 開啓
線程pool-1-thread-2 開啓
主線程在執行任務
多線程執行結果:5050
程序執行時間:5

Process finished with exit code 0

 

第二種,上面的方案是基於線程池進行的提交操作,如果不想用線程池怎麼辦?可以使用FutureTask類來實現,FutureTask類實現了RunnableFuture接口,RunnableFuture接口繼承了Runnable接口和Future接口,FutureTask是Future接口唯一的實現類;

FutureTask實現了Future接口,就可以調用其get方法,獲取返回結果;

FutureTask實現了Runnable接口,就可以把它傳入Thread類中執行,這樣就躲開了線程池;當然Future由於間接實現了Future,用線程池的submit也是可以的。

public class TestFutureTask {
    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        //第一種方式,通過線程池提交任務,簡便起見,直接計算1-100了
        ExecutorService pool = Executors.newCachedThreadPool();
        CalculateTask task1 = new CalculateTask(1,50);
        FutureTask<Integer> futureTask1 = new FutureTask<Integer>(task1);
        CalculateTask task2 = new CalculateTask(50,101);
        FutureTask<Integer> futureTask2 = new FutureTask<Integer>(task2);
        pool.submit(futureTask1);
        pool.submit(futureTask2);
        pool.shutdown();

        /*第二種方式,由於FutureTask實現了RunnableFuture,而FutureRunnable接口
        * 而FutureRunnable接口又繼承了Future和Runnable接口
        * 因此,可以通過Thread的方式啓動
        */
        FutureTask<Integer> futureTask3 = new FutureTask<Integer>(task1);
        Thread thread1 = new Thread(futureTask3);
        thread1.start();
        FutureTask<Integer> futureTask4 = new FutureTask<Integer>(task2);
        Thread thread2 = new Thread(futureTask4);
        thread2.start();
        System.out.println("主線程執行任務");
        try {
            System.out.println("第一種方式獲取計算結果:" + (futureTask1.get() + futureTask2.get()));
            System.out.println("第二種方式獲取計算結果" + (futureTask3.get() + futureTask4.get()));
            long end = System.currentTimeMillis();
            System.out.println("程序執行時間:" + (end-startTime)/1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

以上代碼提供了兩種方式,使用線程池和使用Thread的方式啓動,結果是相同的:

線程pool-1-thread-1 開啓
線程pool-1-thread-2 開啓
線程Thread-0 開啓
主線程執行任務
線程Thread-1 開啓
第一種方式獲取計算結果:5050
第二種方式獲取計算結果5050
程序執行時間:5

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章