java線程獲取結果Callable、Future、FutureTask

java線程獲取結果Callable、Future、FutureTask

一、提個問題

     當我們創建一個線程時,我們想獲取線程運行完成後的結果,是否可以用回調的方法來實現?

答案是肯定的,可以。例如:

//定義一個回調接口
interface Callable {
    void call(int num);
}


public class FutureTest {


    public static void main(String[] args)  {
         
       //創建回調對象
        Callable callable = new Callable() {
            @Override
            public void call(int num) {
                System.out.println("線程運行結果值 num=="+num);
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("線程"+Thread.currentThread().getName()+" 開始");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //調用回調方法  把結果傳出去
                callable.call(100);
                System.out.println("線程"+Thread.currentThread().getName()+" 結束");
            }
        }, "t1").start();

        System.out.println("主線程結束");

    }
}

運行結果:

主線程結束
線程t1 開始
線程運行結果值 num==100
線程t1 結束

這種方式的實現有三個缺點:

1、必須要創建回調接口。而且線程運行中可能產生異常,那麼回調接口至少包含成功回調和錯誤回調兩個方法。
2、當線程運行後,只能等到線程回調接口,本身我們沒有辦法進行取消操作。
3、如果要重複獲取同樣線程運行結果的值,還是隻能重新運行線程。當然你也可以使用一個變量緩存結果值。

那麼有沒有一種優化的方式呢?java中提供了FutureCallable的模式。

 

二、理解 Runnable、CallableFuture

    先看一下定義

   Runnable

          Runnable它只有一個run()函數,用於將耗時操作寫在其中,該函數沒有返回值。然後使用某個線程去執行該Runnable即可實現多線程,Thread類在調用start()函數後就是執行的是Runnablerun()函數.

public interface Runnable {
    /*
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

   Callable

           Callable中有一個call()函數,但是call()函數有返回值,而Runnable的run()函數不能將結果返回給客戶程序。

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

 Callable 與Runnable的區別  

    1、Callable定義的方法是call,而Runnable定義的方法是run。
    2、Callable的call方法可以有返回值,而Runnable的run方法不能有返回值。
    3、 Callable的call方法可拋出異常,而Runnable的run方法不能拋出異常

 

   Future

           Executor就是RunnableCallable的調度容器,Future就是對於具體的Runnable或者Callable任務的執行結果進行

取消、查詢是否完成、獲取結果、設置結果操作進行的再次封裝。注意其get方法會阻塞,直到任務返回結果。

public interface Future<V> {
 
    /**
     * Attempts to cancel execution of this task.  This attempt will
     * fail if the task has already completed, has already been cancelled,
     * or could not be cancelled for some other reason. If successful,
     * and this task has not started when <tt>cancel</tt> is called,
     * this task should never run.  If the task has already started,
     * then the <tt>mayInterruptIfRunning</tt> parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.
     */
    boolean cancel(boolean mayInterruptIfRunning);
 
    /**
     * Returns <tt>true</tt> if this task was cancelled before it completed
     * normally.
     */
    boolean isCancelled();
 
    /**
     * Returns <tt>true</tt> if this task completed.
     *
     */
    boolean isDone();
 
    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     *
     * @return the computed result
     */
    V get() throws InterruptedException, ExecutionException;
 
    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

      Future<V>接口是用來獲取異步計算結果的,說白了就是對具體的Runnable或者Callable對象任務執行的結果進行獲取(get()),取消(cancel()),判斷是否完成等操作

Future接口中聲明瞭5個方法,下面依次解釋每個方法的作用:   

  1、cancel方法  用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。

參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。

如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;

如果任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning爲true還是false,肯定返回true。

   2、isCancelled方法  表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。

   3、isDone方法   表示任務是否已經完成,若任務完成,則返回true;

   4、get()方法    用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;

   5、get(long timeout, TimeUnit unit)    用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。

也就是說Future提供了三種功能:   

1)判斷任務是否完成;

2)能夠中斷任務;

3)能夠獲取任務執行結果。

因爲Future只是一個接口,所以是無法直接用來創建對象使用的,因此就有了下面的FutureTask

 

FutureTask

看一下FutureTask的實現類

public class FutureTask<V> implements RunnableFuture<V>

說明FutureTask類實現了RunnableFuture接口,我們看一下RunnableFuture接口的實現

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現了RunnableFuture接口。所以FutureTask既可以作爲Runnable被線程執行,又可以作爲Future得到Callable的返回值

 

另外FutureTaslk還可以包裝RunnableCallable<V>, 由構造函數注入依賴

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
 
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

上面代碼塊可以看出:

Runnable注入會被Executors.callable()函數轉換爲Callable類型,即FutureTask最終都是執行Callable類型的任務

因此FutureTask是Future也是Runnable,又是包裝了的Callable( 如果是Runnable最終也會被轉換爲Callable )。

 

Callable 和 Future接口的區別

 1、Callable規定的方法是call(),而Runnable規定的方法是run(). 
 2、Callable的任務執行後可返回值,而Runnable的任務是不能返回值的。  
 3、call()方法可拋出異常,而run()方法是不能拋出異常的。 
 4、運行Callable任務可拿到一個Future對象, Future表示異步計算的結果。 
 5、它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。 
 6、 通過Future對象可瞭解任務執行情況,可取消任務的執行,還可獲取任務執行的結果。 
 7、Callable是類似於Runnable的接口,實現Callable接口的類和實現Runnable的類都是可被其它線程執行的任務。


三、解決問題

      搞清楚了概念我們就來解決文章開頭提出的問題

    1、使用Callable+Future獲取執行結果

package com.demo.test;

import java.util.concurrent.Callable;

//定義一個任務
public 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;
    }

}

 

package com.demo.test;

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

public class CallableTest {

    public static void main(String[] args) {
        //創建線程池
        ExecutorService executor = Executors.newCachedThreadPool();
        //創建Callable對象任務  
        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 {
            if(result.get()!=null){  
                System.out.println("task運行結果"+result.get());
            }else{
                System.out.println("未獲取到結果"); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
         
        System.out.println("所有任務執行完畢");
    }
}

  2、使用Callable+FutureTask獲取執行結果

package com.demo.test;

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

public class CallableTest1 {
    
    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 {
            if(futureTask.get()!=null){  
                System.out.println("task運行結果"+futureTask.get());
            }else{
                System.out.println("future.get()未獲取到結果"); 
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
         
        System.out.println("所有任務執行完畢");
    }
}

 

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