FutureTask簡單實戰

FutureTask是什麼?

線程池的實現核心之一是FutureTask。在提交任務時,用戶實現的Callable實例task會被包裝爲FutureTask實例ftask;提交後任務異步執行,無需用戶關心;當用戶須要時,再調用FutureTask#get()獲取結果——或異常。html

基本使用

方法中可能會調用到多個服務/方法,且這些服務/方法之間是互相獨立的,不存在前後關係。在高併發場景下,若是執行比較耗時,能夠考慮多線程異步的方式調用。java

咱們先模擬兩個耗時服務

一個150ms,一個200ms多線程

public class UserApi {

    /** 查詢用戶基本信息,模擬耗時150ms */
    public String queryUserInfo(long userId) {
        String userInfo = "userInfo: " + userId;

        try {
            TimeUnit.MILLISECONDS.sleep(150L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userInfo;
    }

    /** 查詢用戶地址,模擬耗時200ms */
    public String queryUserAddress(long userId) {
        String userAddress = "userAddress: " + userId;

        try {
            TimeUnit.MILLISECONDS.sleep(200L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userAddress;
    }
}

不使用FutureTask

@Test
public void testNotUseFutureTask() {
    UserApi userApi = new UserApi();

    long userId = 12;
    long startTime = System.currentTimeMillis();

    // 獲取用戶基本信息
    String userInfo = userApi.queryUserInfo(userId);
    // 獲取用戶地址
    String userAddress = userApi.queryUserAddress(userId);

    System.err.println("testNotUseFutureTask 耗時:" + (System.currentTimeMillis() - startTime));
}

執行幾回,結果:併發

testNotUseFutureTask 耗時:358
testNotUseFutureTask 耗時:360

從結果中,能夠看到,總耗時是大於queryUserInfoqueryUserAddress之和的。但這兩個服務邏輯上並不存在前後關係,理論上最長耗時取決於最慢的那個,即queryUserAddress異步

使用FutureTask

下例使用了FutureTask,來異步調用queryUserInfoqueryUserAddresside

@Test
public void testUseFutureTask() throws ExecutionException, InterruptedException {
    UserApi userApi = new UserApi();

    long userId = 12;
    long startTime = System.currentTimeMillis();

    Callable<String> userInfoCallable = new Callable<String>() {
        @Override
        public String call() throws Exception {
            return userApi.queryUserInfo(userId);
        }
    };
    Callable<String> userAddressCallable = new Callable<String>() {
        @Override
        public String call() throws Exception {
            return userApi.queryUserAddress(userId);
        }
    };
    FutureTask<String> userInfoFutureTask = new FutureTask<>(userInfoCallable);
    FutureTask<String> userAddressFutureTask = new FutureTask<>(userAddressCallable);

    new Thread(userInfoFutureTask).start();
    new Thread(userAddressFutureTask).start();

    String userInfo = userInfoFutureTask.get();
    String userAddress = userAddressFutureTask.get();
    System.err.println("testUseFutureTask 耗時:" + (System.currentTimeMillis() - startTime));
}

執行幾回,結果:函數

testUseFutureTask 耗時:239
testUseFutureTask 耗時:237

很明顯,總耗時大大減小了,這就驗證了前面所說,總耗時取決於queryUserAddress的耗時。高併發

實現一個簡單的FutureTask

從前面使用FutureTask的代碼中能夠看到,一個FutureTask須要包含如下幾點:學習

一、範型
二、構造函數,傳入Callable
三、實現Runnable
四、有返回值
  • MyFutureTask代碼以下:
public class MyFutureTask<T> implements Runnable {

    private Callable<T> callable;
    private T           result;
    private String      state;

    public MyFutureTask(Callable<T> callable) {
        this.callable = callable;
    }

    @Override
    public void run() {
        state = "NEW";
        try {
            result = callable.call();
        } catch (Exception e) {
            e.printStackTrace();
        }
        state = "DONE";
        synchronized (this) {
            this.notify();
        }
    }

    /** 獲取調用結果 */
    public T get() throws InterruptedException {
        if ("DOEN".equals(state)) {
            return result;
        }
        synchronized (this) {
            this.wait();
        }
        return result;
    }
}
  • 使用:
@Test
public void testMyUseFutureTask() throws InterruptedException {
    UserApi userApi = new UserApi();

    long userId = 12;
    long startTime = System.currentTimeMillis();

    Callable<String> userInfoCallable = new Callable<String>() {
        @Override
        public String call() throws Exception {
            return userApi.queryUserInfo(userId);
        }
    };
    Callable<String> userAddressCallable = new Callable<String>() {
        @Override
        public String call() throws Exception {
            return userApi.queryUserAddress(userId);
        }
    };
    
    // 不一樣點
    MyFutureTask<String> userInfoFutureTask = new MyFutureTask<>(userInfoCallable);
    MyFutureTask<String> userAddressFutureTask = new MyFutureTask<>(userAddressCallable);

    new Thread(userInfoFutureTask).start();
    new Thread(userAddressFutureTask).start();

    String userInfo = userInfoFutureTask.get();
    String userAddress = userAddressFutureTask.get();
    System.err.println("testMyUseFutureTask 耗時:" + (System.currentTimeMillis() - startTime));
}
  • 輸出結果:
testMyUseFutureTask 耗時:208
testMyUseFutureTask 耗時:211

從結果中看到,預期與使用FutureTask的一致。至於使用咱們自定義的MyFutureTask執行耗時爲什麼會比FutureTask長,我猜想是咱們本身寫的未作更多的檢查和判斷。咱們本身寫的只是用來學習FutureTaskthis

總結

不使用異步的方式時,queryUserAddressqueryUserInfo執行以後纔會執行,二者相加的時間算入總調用耗時。若是使用了異步線程調用,因爲queryUserAddress耗時長,這樣在queryUserAddress執行結束前,queryUserInfo就執行結束了,這樣queryUserInfo調用耗時就不計了。

原文出處:https://www.cnblogs.com/flylinran/p/10171449.html

相關文章
相關標籤/搜索