1. 程式人生 > 程式設計 >Java多執行緒開發 - Future的使用與理解

Java多執行緒開發 - Future的使用與理解

著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。

Future模式是什麼

Future模式是多執行緒開發中非常常見的一種設計模式。它的核心思想是非同步呼叫。當我們需要呼叫一個函式方法時。如果這個函式執行很慢,那麼我們就要進行等待。但有時候,我們可能並不急著要結果。因此,我們可以讓被呼叫者立即返回,讓他在後臺慢慢處理這個請求。對於呼叫者來說,則可以先處理一些其他任務,在真正需要資料的場合再去嘗試獲取需要的資料。

用生活中的例子來打個比喻,就像叫外賣。比如在午休之前我們可以提前叫外賣,只需要點好食物,下個單。然後我們可以繼續工作。到了中午下班的時候外賣也就到了,然後就可以吃個午餐,再美滋滋的睡個午覺。而如果你在下班的時候才叫外賣,那就只能坐在那裡乾等著外賣小哥,最後拿到外賣吃完午飯,午休時間也差不多結束了。

使用Future模式,獲取資料的時候無法立即得到需要的資料。而是先拿到一個契約,你可以在將來需要的時候再用這個契約去獲取需要的資料,這個契約就好比叫外賣的例子裡的外賣訂單。

普通方式和Future模式的區別

我們可以看一下使用普通模式和用Future模式的時序圖。可以看出來普通模式是序列的,在遇到耗時操作的時候只能等待。而Future模式,只是發起了耗時操作,函式立馬就返回了,並不會阻塞客戶端執行緒。所以在工作執行緒執行耗時操作的時候客戶端無需等待,可以繼續做其他事情,等到需要的時候再向工作執行緒獲取結果:

Future模式的簡單實現

首先是FutureData,它只是一個包裝類,建立它不需要耗時。在工作執行緒準備好資料之後可以使用setData

方法將資料傳入。而客戶端執行緒只需要在需要的時候呼叫getData方法即可,如果這個時候資料還沒有準備好,那麼getData方法就會等待,如果已經準備好了就好直接返回。

public class FutureData<T> {
    private boolean mIsReady = false;
    private T mData;

    public synchronized void setData(T data) {
        mIsReady = true;
        mData = data;

        notifyAll();
    }

    public synchronized T getData
() { while (!mIsReady) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return mData; } } 複製程式碼

接著是服務端,客戶端在向服務端請求資料的時候服務端不會實際去載入資料,它只是建立一個FutureData,然後建立子執行緒去載入,而它只需要直接返回FutureData就可以了。

public class Server {
    public FutureData<String> getString() {
        final FutureData<String> data = new FutureData<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data.setData("world");
            }
        }).start();

        return data;
    }
}
複製程式碼

客戶端程式碼如下,整個程式只需要執行2秒多,但如果不使用Future模式的話就需要三秒了。

Server server = new Server();
FutureData<String> futureData = server.getString();

//先執行其他操作
String hello = "hello";
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

System.out.print(hello + " " + futureData.getData());
複製程式碼

JDK中的Future模式

public interface Executor {
    void execute(Runnable command);
}

public interface ExecutorService extends Executor {
    ...
}
複製程式碼

execute方法其實是在Executor中定義的,而ExecutorService繼承了Executor。它只是簡單的提交了一個Runnable給執行緒池中的執行緒去呼叫:

public interface ExecutorService extends Executor {
    ...
    <T> Future<T> submit(Callable<T> task);
        
    <T> Future<T> submit(Runnable task,T result);
        
    Future<?> submit(Runnable task);
    ...
}
複製程式碼

而submit方法是ExecutorService中定義的,它們都會返回一個Future物件。實際上submit方法就是使用的Future模式:

  • Future<?> submit(Runnable task) :
    它的返回值實際上是Future,子執行緒是不會返回資料的。
  • Future submit(Runnable task,T result) :
    這個方法是不是很蛋疼,返回的結果在呼叫的時候已經給出了。如果我一開始就知道結果那我為什麼又要發起子執行緒呢?

其實不然,這個result可以是一個代理,它不是實際的結果,它只是儲存了結果。我這裡給出一個例子大家體會一下吧:

final String[] result = new String[1];

Runnable r = new Runnable() {
    public void run() {
        result[0] = "hello world";
    }
};

Future<String[]> future = Executors.newSingleThreadExecutor().submit(r,result);
    
try {
    System.out.println("result[0]: " + future.get()[0]);
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

結果:result[0]: hello world
複製程式碼
  • Future submit(Callable task)
    這個方法就比較好理解了,Callable.call()方法在子執行緒中被呼叫,同時它有返回值,只要將載入的資料直接return出來就好:
Future<String> future = Executors.newSingleThreadExecutor()
        .submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "Hello World";
            }
        });

try {
    System.out.print(future.get());
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

結果:Hello World
複製程式碼

實戰演示

比如我們在計算兩個List中的數的總和的時候就可以用Future模式提高效率:

public int getTotal() throws ExecutionException,InterruptedException {
    ArrayList<Integer> a = ListUtil.newArrayList(1,1);
    ArrayList<Integer> b = ListUtil.newArrayList(1,1,1);
    Future<Integer> future = Executors.newCachedThreadPool().submit(new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            int r = 0;
            for (int num : a) {
                r += num;
            }
            return r;
        }
    });

    int r = 0;
    for (int num : b) {
        r += num;
    }
    return r + future.get();
}

結果:5
複製程式碼

掃碼關注更多內容