1. 程式人生 > >[Java併發程式設計實戰] Future+callable+FutureTask 閉鎖機制(含示例程式碼)

[Java併發程式設計實戰] Future+callable+FutureTask 閉鎖機制(含示例程式碼)

業精於勤,荒於嬉;行成於思,毀於隨。—韓愈
它告訴我們,事業的成功在於奮發努力,勤勉進取,太貪玩,放鬆要求便會一事無成;做事情要想成功,需要反覆思考、深思熟慮,而隨手隨意、隨隨便便行事,做事不經過大腦,必然招致失敗。

FutureTask 也可以做閉鎖,它是 Future 和 callable 的結合體。所以我們有必要來了解 FutureTask 這個類。

FutureTask 的繼承關係類圖

先看 FutureTask 類的繼承:

public class FutureTask<V> implements RunnableFuture<V> 

它繼承自 RunnableFuture,可以看出他是 Runnable 和 Future 的結合體。

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

我們熟悉的 Runnable 介面:

public interface Runnable
{
public abstract void run(); }

不常見的Future 介面,用來獲取非同步計算結果:

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 {@code cancel} is called,
     * this task should never run.  If the task has already started,
     * then the {@code mayInterruptIfRunning} parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.
     */
boolean cancel(boolean mayInterruptIfRunning); /** * Returns {@code true} if this task was cancelled before it completed * normally. */ boolean isCancelled();//如果任務被取消,返回true /** * Returns {@code true} if this task completed. */ boolean isDone();//如果任務執行結束,無論是正常結束或是中途取消還是發生異常,都返回true。 /** * Waits if necessary for the computation to complete, and then * retrieves its 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. */ V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }

到這裡,FutureTask 整個繼承關係已經很清楚了。為了更直觀一點,我用 starUML 畫出它的類繼承關係圖。

這裡寫圖片描述

在類關係圖中,我們可以看到 FutureTask 的建構函式,包含了之前沒有見過的型別:Callable。我們直接看下它的兩個建構函式實現,進一步瞭解看看:

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

這裡已經非常清楚了,最終都是賦值給 FutureTask 的內部變數 callable。它是一個介面,包含一個有返回值的函式 call()。

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

通過上面的講解,我們已經知道 Future,FutureTask,Callable,Runnable的關係了。那麼,說了這麼多主要是想幹嘛呢?

沒錯,主要就是為了執行緒執行完成後能夠返回結果。我們知道,Runnable 介面執行完成後,是沒法返回結果的。所以,我們如果想要能夠返回執行的結果,必須使用 callable 介面。

應用場景

比如我們有個耗時的計算操作,現在建立一個子執行緒執行計算操作,主執行緒通過 FutureTask.get() 的方式獲取計算結果,如果計算還沒有完成,則會阻塞一直等到計算完成

下面我們直接編寫程式碼來實現上面的應用場景。

使用 Callable + FutureTask 獲取執行結果:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureTaskTest{

    //建立一個Future物件,並把Callable的實現傳給建構函式
    private static final FutureTask<Integer> future = new FutureTask<Integer>(new CallableTest());

    public static void main(String[] args) {
        //建立一個執行緒
        final Thread thread = new Thread(future);
        //啟動執行緒
        thread.start();
        try {
            Thread.sleep(1000);
            System.out.println("Main thread is running");
            //獲取計算結果,會阻塞知道計算完畢
            System.out.println("get the sub thread compute result : " + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("main thread is end");
    }

    //實現Callable介面,耗時操作
    static class CallableTest implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            int ret = 0;
            Thread.sleep(1000);
            System.out.println("sub thread is computing");
            for(int i = 0; i < 1000; i++) {
                ret += i;
            }
            System.out.println("sub thread is finish compute");
            return ret;
        }   
    }
}

執行結果:
這裡寫圖片描述

另外一種方式,是使用 Callable + Future + ExecutorService 的方式。ExecutorService繼承自Executor,它的目的是為我們管理Thread物件,從而簡化併發程式設計,Executor使我們無需顯示的去管理執行緒的生命週期。

在ExecutorService介面中聲明瞭若干個submit方法的過載版本:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task); 

第一個submit方法裡面的引數型別就是Callable。

示例如下:

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;
import java.util.concurrent.FutureTask;

public class FutureTaskTest{
    public static void main(String[] args) {
        //返回一個執行緒池,通常都和這種執行緒寬架搭配
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        System.out.println("Main thread is running");
        //提交給執行緒,返回一個Future類,並執行
        Future<Integer> future = threadPool.submit(new CallableTest());
        try {
            Thread.sleep(1000);
            //獲取計算結果,會阻塞知道計算完畢
            System.out.println("get the sub thread compute result : " + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("main thread is end");
    }
    //實現Callable介面,耗時操作
    static class CallableTest implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            int ret = 0;
            Thread.sleep(1000);
            System.out.println("sub thread is computing");
            for(int i = 0; i < 1000; i++) {
                ret += i;
            }
            System.out.println("sub thread is finish compute");
            return ret;
        }   
    }
}

執行結果
這裡寫圖片描述

本文完結,希望看完對你有幫助,歡迎關注我~

本文原創首發於微信公眾號 [ 林裡少年 ],歡迎關注第一時間獲取更新。