Java併發編程之Future和FutureTask

搞過Java或者客戶端Android開發的都知道,建立線程的2種方式,一種是直接繼承Thread,另一種就是實現Runnable接口。不過,這2種方式都有一個缺陷,就是在執行完任務以後沒法獲取執行結果。java

若是須要獲取執行結果,就必須經過共享變量或者使用線程通訊的方式來達到效果,這就涉及到線程切換和線程通訊等問題,就比較的麻煩。 不過,好在Java 從1.5版本開始,就提供了Callable和Future,經過它們能夠在任務執行完畢以後獲得任務執行結果。bash

Callable與Runnable

先看一下java.lang.Runnable,Runnable是一個接口,它裏面只聲明瞭一個run()方法。代碼以下:多線程

public interface Runnable {
    public abstract void run();
}
複製代碼

因爲run()方法是一個void類型的,因此在執行完任務以後沒法返回任何結果。異步

Callable位於java.util.concurrent包下,它也是一個接口,它裏面也只聲明瞭一個方法,只不過這個方法叫作call()。代碼以下:ide

public interface Callable<V> {
    V call() throws Exception;
}
複製代碼

能夠發現,Callable接受一個泛型,call()函數返回的類型就是傳遞進來的V類型。 那麼怎麼使用Callable呢?通常狀況下Callable須要和ExecutorService配合使用,在ExecutorService接口中聲明瞭若干個submit方法的重載版本。函數

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
複製代碼

其中,第一個submit方法裏面的參數類型就是Callable。如下是一個完整的使用示例:ui

package thread.learn;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableAndFuture {
    static class MyThread implements Callable<String> {
        @Override
        public String call() throws Exception {
            return "Hello world";
        }
    }

    static class MyThread2 implements Runnable {
        @Override
        public void run() {

        }
    }

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        Future<String> future = threadPool.submit(new MyThread());

        try {
            System.out.println(future.get());
        } catch (Exception e) {

        } finally {
            threadPool.shutdown();
        }
    }
}
複製代碼

Future

Future表示一個可能尚未完成的異步任務的結果,針對這個結果能夠添加Callback以便在任務執行成功或失敗後做出相應的操做。必要時能夠經過get方法獲取執行結果,該方法會阻塞直到任務返回結果。spa

Future類位於java.util.concurrent包下,它也是一個接口,定義以下:線程

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}
複製代碼

能夠發現,Future接口中聲明瞭5個方法,具體的含義以下:code

  • cancel():cancel方法用來取消任務,若是取消任務成功則返回true,若是取消任務失敗則返回false。參數mayInterruptIfRunning表示是否容許取消正在執行卻沒有執行完畢的任務,若是設置true,則表示能夠取消正在執行過程當中的任務。若是任務已經完成,則不管mayInterruptIfRunning爲true仍是false,此方法確定返回false,即若是取消已經完成的任務會返回false;若是任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;若是任務尚未執行,則不管mayInterruptIfRunning爲true仍是false,確定返回true。
  • isCancelled():isCancelled方法表示任務是否被取消成功,若是在任務正常完成前被取消成功,則返回 true。
  • isDone():isDone方法表示任務是否已經完成,若任務完成,則返回true。
  • get():get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回。
  • get(long timeout, TimeUnit unit)用來獲取執行結果,若是在指定時間內,還沒獲取到結果,就直接返回null。

綜上能夠發現,Future具備3種能力: 1 )判斷任務是否完成; 2)可以中斷任務; 3)可以獲取任務執行結果。

同時,Future有4個子類,類圖結構以下:

在這裏插入圖片描述
RunnableFuture RunnableFuture接口同時繼承Future接口和Runnable接口,並在成功執行run方法返回執行結果。這個接口的實現類有FutureTask,一個可取消的異步計算類。 FutureTask能用來包裝一個Callable或Runnable對象,由於它實現了Runnable接口,並且它能被傳遞到Executor進行執行。爲了提供單例類,這個類在建立自定義的工做類時提供了protected構造函數。

ScheduledFuture ScheduledFuture接口表示一個延時的行爲能夠被取消,一般須要將一個安排好的Future配合定時任務SchedualedExecutorService執行,並返回執行的結果。

CompleteFuture Complete表示操做已完成,因此CompleteFuture表示一個異步操做已完成的結果。當兩個或多個線程要執行完成或取消操做時,只有一個可以成功。

ForkJoinPool ForkJoinPool是一個基於任務的抽象類,能夠經過ForkJoinPool來執行。一個ForkJoinTask是相似於線程實體,可是相對於線程實體是輕量級的。大量的任務和子任務會被ForkJoinPool池中的真實線程掛起來,以某些使用限制爲代價。

FutureTask

先來看一下FutureTask的實現,源碼以下:

public class FutureTask<V> implements RunnableFuture<V>
複製代碼

FutureTask類實現了RunnableFuture接口,再看一下RunnableFuture接口的實現。源碼以下:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
複製代碼

能夠看出,RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現了RunnableFuture接口。因此它既能夠做爲Runnable被線程執行,又能夠做爲Future獲得Callable的返回值。

示例1

Callable+Future獲取多線程的執行結果。

public class FutureDemo {

    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        executor.shutdown();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

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

        try {
            System.out.println("task運行結果"+result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("全部任務執行完畢");
    }
}

class Task implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("子線程在進行計算");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++){
            sum += i;
        }
        return sum;
    }
}
複製代碼

執行結果:

子線程在進行計算
主線程在執行任務
task運行結果4950
全部任務執行完畢
複製代碼

示例2

使用Callable+FutureTask獲取多線程的執行結果。

public class FutureTask {

    public static void main(String[] args) {
        //第一種方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executor.submit(futureTask);
        executor.shutdown();

        //第二種方式,注意這種方式和第一種方式效果是相似的,只不過一個使用的是ExecutorService,一個使用的是Thread
        /*Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        Thread thread = new Thread(futureTask);
        thread.start();*/

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

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

        try {
            System.out.println("task運行結果"+futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("全部任務執行完畢");
    }
}


class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("子線程在進行計算");
        Thread.sleep(3000);
        int sum = 0;
        for(int i=0;i<100;i++){
            sum += i;
        }
        return sum;
    }
}
複製代碼

示例3

例如,下面是一個典型的多線程場景:好比去吃早點時,點了包子和涼菜,包子須要等3分鐘,涼菜只需1分鐘,若是是串行的一個執行,在吃上早點的時候須要等待4分鐘,可是由於你在等包子的時候,能夠同時準備涼菜,因此在準備涼菜的過程當中,能夠同時準備包子,這樣只須要等待3分鐘。

public class FutureTaskDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        // 等涼菜
        Callable ca1=new Callable() {
            @Override
            public Object call() throws Exception {
                try {
                    Thread.sleep(1000);
                }catch (Exception e){
                  e.printStackTrace();
                }
                return "等涼菜";
            }
        };

        FutureTask<String> ft1 = new FutureTask<String>(ca1);
        new Thread(ft1).start();

        //等包子
        Callable ca2=new Callable() {
            @Override
            public Object call() throws Exception {
                try {
                    Thread.sleep(3000);
                }catch (Exception e){
                    e.printStackTrace();
                }
                return "等包子";
            }
        };


        FutureTask<String> ft2 = new FutureTask<String>(ca2);
        new Thread(ft2).start();

        System.out.println(ft1.get());
        System.out.println(ft2.get());

        long end = System.currentTimeMillis();

        System.out.println(end-start);
    }
}
複製代碼
相關文章
相關標籤/搜索