JAVA多線程高併發學習筆記(三)——Callable、Future和FutureTask

爲何要是用Callable和Future

Runnable的侷限性

Executor採用Runnable做爲基本的表達形式,雖然Runnable的run方法可以寫入日誌,寫入文件,寫入數據庫等操做,可是它不能返回一個值,或者拋出一個受檢查的異常,有些須要返回值的需求就不能知足了。java

可以取消

Executor中的任務有四個狀態:建立,提交,開始和完成。若是說有些任務執行時間比較長,但願可以取消該任務,Executor中的任務在未開始前是能夠取消的,若是已經開始了,只能經過中斷的方式來取消。若是使用Callable和Future的結合,可使用Future的canel方法取消任務,這樣就方便多了。數據庫

 

一個例子:

import java.util.concurrent.*;

public class Demo1 {

    public static void main(String args[]) throws Exception {
        ServiceTask task = new ServiceTask();
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<Integer> result = executor.submit(task);
        executor.shutdown();
        System.out.println("正在執行任務");
        Thread.sleep(1000);
        System.out.println("task運行結果爲:" + result.get());
    }
}

class ServiceTask implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Thread.sleep(2000);
        int result = 0;
        // 假設一個很龐大的計算
        for(int i=1;i<100;i++){
            for (int j=0;j<i;j++){
                result +=j;
            }
        }
        return result;
    }
}

看一下執行結果:編程

這個例子就是一個很是簡單的使用Callable和Futute的例子,ServiceTask類實現了Callable接口,並返回一個Integer類型的值。併發

Future<Integer> result = executor.submit(task);這行代碼就是構造一個Future。使用其get()方法就能獲得最後的運行值。框架

 好了看完這一個簡單的例子,那就來仔細瞭解一下它們。ide

瞭解Callable和Future

Callable

 來看一下callable的代碼:性能

public abstract interface Callable<V> {
    public abstract V call() throws Exception;
}

能夠看出它是接口,提到接口就能夠明白接口是靈活的,支持傳入泛型參數。這個沒什麼,咱們來重點介紹一下Futurespa

Future

首先來看關於它的介紹
Future提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。計算完成後只能使用 get 方法來獲取結果,若有必要,計算完成前能夠阻塞此方法。取消則由 cancel 方法來執行。還提供了其餘方法,以肯定任務是正常完成仍是被取消了。線程

來看一下Future的代碼日誌

public abstract interface Future<V> {
    public abstract boolean cancel(boolean paramBoolean);

    public abstract boolean isCancelled();

    public abstract boolean isDone();

    public abstract V get() throws InterruptedException, ExecutionException;

    public abstract V get(long paramLong, TimeUnit paramTimeUnit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

提供了五個方法

 

public abstract boolean cancel(boolean paramBoolean)

試圖取消任務的執行(注意是試圖),由於存在一些任務已完成、已取消或者由於某些緣由沒法取消的因素,存在着取消失敗的可能性。

當canel方法起做用時,有兩個狀況:

1.任務未開始,則該任務將永遠不會運行;

2.任務處於執行狀態,paramBoolean表示是否採用中斷的方式中斷線程。

 

public abstract boolean isCancelled()

若是任務正常取消的,則返回true。

 

 

public abstract boolean isDone();

若是任務已完成,則返回 true。 可能因爲正常終止、異常或取消而完成,在全部這些狀況中,此方法都將返回 true

(注意若是調用isCanle方法,那麼isDone將始終返回true).

 

public abstract V get() throws InterruptedException, ExecutionException;

重點到了!這是Future獲取計算結果的方式之一,使用get方法。(注意這裏返回的是Callable中的泛型)

 get方法取決於任務的狀態(未開始,運行中,已完成),若是任務已經完成,那麼get會當即返回或者拋出一個異常;

若是任務沒有完成,那麼get將阻塞知道任務完成。若是任務拋出了異常,那麼get會將該異常封裝成ExecutionException拋出。

 

public abstract V get(long paramLong, TimeUnit paramTimeUnit) throws InterruptedException, ExecutionException, TimeoutException;

若是須要在給定時間後獲取計算結果,可使用這個方法,若是超過給定時間以後沒有獲得計算結果,則拋出TimeoutException。(注意這裏返回的是Callable中的泛型)  

 

如何使用

來看代碼:

import java.util.concurrent.*;

public class Demo1 {

    public static void main(String args[]) throws Exception {
        // 1.先實例化任務對象
        ServiceTask task = new ServiceTask();
        // 2.實例化Executor框架中的線程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // 3.使用submit方法將任務提交(返回的是一個Future)
        Future<Integer> result = executor.submit(task);
        // 4.記得關閉線程池
        executor.shutdown();
        System.out.println("正在執行任務");
        Thread.sleep(1000);
        // 5.打印最後的結果
        System.out.println("task運行結果爲:" + result.get());
    }
}

/**
 * Callable的實現類
 */
class ServiceTask implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        Thread.sleep(2000);
        int result = 0;
        // 假設一個很龐大的計算
        for(int i=1;i<100;i++){
            for (int j=0;j<i;j++){
                result +=j;
            }
        }
        return result;
    }
}

運行結果:

接下來咱們來試一下定時取結果:

仍是在原來的代碼上修改:

import java.util.concurrent.*;

public class Demo1 {

    public static void main(String args[]) throws Exception {
        // 1.先實例化任務對象
        ServiceTask task = new ServiceTask();
        // 2.實例化Executor框架中的線程池
        ExecutorService executor = Executors.newCachedThreadPool();
        // 3.使用submit方法將任務提交(返回的是一個Future)
        Future<Integer> result = executor.submit(task);
        // 4.記得關閉線程池
        executor.shutdown();
        System.out.println("正在執行任務");
        Thread.sleep(1000);
        // 5.設置定時一秒取結果
        System.out.println("task運行結果爲:" + result.get(1,TimeUnit.MILLISECONDS));
    }
}

/**
 * Callable的實現類
 */
class ServiceTask implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        //這裏睡眠2秒
        Thread.sleep(2000);
        int result = 0;
        // 假設一個很龐大的計算
        for(int i=1;i<100;i++){
            for (int j=0;j<i;j++){
                result +=j;
            }
        }
        return result;
    }
}

來提早猜測一下,首先設置了定時一秒以後取得結果,可是ServiceTask設置兩秒的睡眠時間,理應取結果失敗,看一下運行結果:

是的,若是在規定時間內沒法取到結果,就會返回TimeoutException。

 

談談FutureTask

FutureTask是Future的實現類,它繼承了RunnableFuture,RunnableFuture實際上繼承了Runnable和Future接口。

來看一下使用如何FutureTask:

import java.util.concurrent.*;

public class FutureCallDemo2 {

    public static void main(String args[])throws  Exception{
        // 1.先實例化任務對象
        FutureTaskService task = new FutureTaskService();
        // 2.實例化Executor框架中的線程池
        ExecutorService excutor = Executors.newCachedThreadPool();
        // 3.直接new一個FutureTask
        FutureTask<Long> result = new FutureTask<Long>(task);
        // 4.提交任務
        excutor.submit(result);
        // 5.關閉線程池
        excutor.shutdown();
        System.out.println("主線程正在執行任務");
        System.out.println("task運行結果爲:" + result.get());
    }
}

/**
 * 繼承Callable接口
 */
class FutureTaskService implements Callable<Long> {

    @Override
    public Long call() throws Exception {
        Thread.sleep(3000);
        // 10的階乘
        long sum = 1;
        for (int i = 1; i <= 10; i++) {
            sum = sum * i;
        }
        return sum;
    }
}

用法的話其實差很少。

 

總結:

Future和Callable能夠實現異構任務,可是有不少值得考慮的地方。

好比一個類使用了兩個任務,一個負責渲染頁面,一個負責下載圖像。

僞代碼以下:

//經過獲取圖像
List<ImageData>ImageDataList = future.get();
for(ImageData data:ImageDataList ){
  //渲染頁面
  renderPage(data);    
}

看似並行的執行任務,可是卻存在着問題。若是說下載圖像的速度遠小於渲染頁面的速度,那麼最終的執行速度就和串行無異了。

因此只有當大量相互獨立且同構的任務能夠進行併發處理時,才能體現出將任務分到多個任務中帶來的性能提高,考慮實際狀況再選擇使用會帶來事半功倍的效果。

 

本文參考:

Java併發編程實戰

相關文章
相關標籤/搜索