Java線程和多線程(十三)——Callable,Future,FutureTask

在Java多線程之中,CallableFuture的使用時非常廣泛的。在之前的文章中,我們瞭解了關於Java線程池基礎的一些內容,知道如何提交Runnable的任務。但是,Runnable的任務是無法有返回值,也不能拋出異常的。而有些時候,我們希望一個線程能夠有一些返回值。在Java 5中,引入了java.util.concurrent.Callable接口,這個接口很類似於Runnable接口,但是可以返回一個對象,或者拋出異常。

Java Callable

Java的Callable接口使用了泛型來定義返回的對象的類型。Executors類提供了一些很實用的方法來在線程池中執行Callable的任務。因爲Callable的任務通過並行的方式來運行,所以我們需要等待返回的對象。

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Java Future

Java的Callable對象返回的就是java.util.concurrent.Future對象。通過使用Java Future對象,我們可以知道Callable任務的執行狀態,並且獲得返回的對象。Future接口提供get()方法來讓開發者可以等待Callable任務的執行,然後獲得對應的結果。

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;
}

Java Future提供了一個cancel()方法來取消關聯的Callable任務的執行。其中的get()方法是包含一個重載的方法的,我們可以指定等待的時間,而不需要無限期的等待Callable任務的執行。這個方法可以有效的防止一個線程的無限期的阻塞。
Future也提供一個isDone()和一個isCancelled()方法來找到其關聯的Callable任務的執行狀態。

下面是使用Callable的例子,是在一秒之後返回執行任務的名字。我們通過使用Executor框架來並行執行100個任務,然後用Future來獲得任務的執行結果。

package com.sapphire.threads;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MyCallable implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        //return the thread name executing this callable task
        return Thread.currentThread().getName();
    }

    public static void main(String args[]){
        //Get ExecutorService from Executors utility class, thread pool size is 10
        ExecutorService executor = Executors.newFixedThreadPool(10);
        //create a list to hold the Future object associated with Callable
        List<Future<String>> list = new ArrayList<Future<String>>();
        //Create MyCallable instance
        Callable<String> callable = new MyCallable();
        for(int i=0; i< 100; i++){
            //submit Callable tasks to be executed by thread pool
            Future<String> future = executor.submit(callable);
            //add Future to the list, we can get return value using Future
            list.add(future);
        }
        for(Future<String> fut : list){
            try {
                //print the return value of Future, notice the output delay in console
                // because Future.get() waits for task to get completed
                System.out.println(new Date()+ "::"+fut.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        //shut down the executor service now
        executor.shutdown();
    }
}

當我們運行上面程序的時候,我們的輸出會有延遲,因爲Future的get()方法會一直等待Callable的任務執行完畢。同時需要注意的是,線程池中,我們僅僅會有10個線程來處理之前定義的Callable任務。

下面是上面程序的輸出結果:

Mon Dec 31 20:40:15 PST 2012::pool-1-thread-1
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-3
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-4
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-5
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-6
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-7
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-8
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-9
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-10
Mon Dec 31 20:40:16 PST 2012::pool-1-thread-2
...

當我們想要覆蓋掉Future接口的一些行爲的時候,舉例來說,假設我們需要覆蓋其中的get()方法來做一些超時處理而不進行持續等待等操作的時候。Java中的FutureTask類在這種時候就會非常有用了,它是Future接口的實現類。

FutureTask

在上面,我們瞭解到使用Callable以及Future接口來處理多線程的一些便利之處。

FutureTaskFuture接口的一個基礎實現,並且提供了異步處理的功能,FutureTask包含了一些方法來啓動或者取消任務,也包含一些方法來返回Future的狀態,來確認Future是完成了還是去掉了。我們需要一個Callable對象來創建一個FutureTask然後,我們可以通過ThreadPoolExecutor來異步處理這些任務。

下面是FutureTask的代碼舉例,因爲FutureTask是需要Callable的,所以我們來創建一個Callable的實現:

package com.sapphire.threads;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {

    private long waitTime;

    public MyCallable(int timeInMillis){
        this.waitTime=timeInMillis;
    }
    @Override
    public String call() throws Exception {
        Thread.sleep(waitTime);
        //return the thread name executing this callable task
        return Thread.currentThread().getName();
    }

}

下面是一個FutureTask方法的例子,下面展示的是關於使用FutureTask方法的一些舉例:

package com.sapphire.threads;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class FutureTaskExample {

    public static void main(String[] args) {
        MyCallable callable1 = new MyCallable(1000);
        MyCallable callable2 = new MyCallable(2000);

        FutureTask<String> futureTask1 = new FutureTask<String>(callable1);
        FutureTask<String> futureTask2 = new FutureTask<String>(callable2);

        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.execute(futureTask1);
        executor.execute(futureTask2);

        while (true) {
            try {
                if(futureTask1.isDone() && futureTask2.isDone()){
                    System.out.println("Done");
                    //shut down executor service
                    executor.shutdown();
                    return;
                }

                if(!futureTask1.isDone()){
                    //wait indefinitely for future task to complete
                    System.out.println(
                        "FutureTask1 output="+futureTask1.get());
                }

                System.out.println("Waiting for FutureTask2 to complete");
                String s = futureTask2.get(200L, TimeUnit.MILLISECONDS);
                if(s !=null){
                    System.out.println("FutureTask2 output="+s);
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }catch(TimeoutException e){
                //do nothing
            }
        }
    }
}

當我們運行上面的程序,你會發現,有一段時間是不會輸出任何東西到控制檯的,因爲FutureTaskget()方法會等待任務的完成,然後纔會返回輸出的對象。在FutureTask中也有一個重載的方法會等待指定的時間。需要注意的是,當調用isDone()方法來確定程序一旦結束,任務也會完成。

輸出如下:

FutureTask1 output=pool-1-thread-1
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
Waiting for FutureTask2 to complete
FutureTask2 output=pool-1-thread-2
Done

從上面的例子來說是沒有使用到FutureTask的便利之處的,但是當我們想要覆蓋掉Future接口方法的實現,而不像實現Future接口的每一個方法的時候,我們就可以考慮使用FutureTask

發佈了44 篇原創文章 · 獲贊 24 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章