線程池的實現核心之一是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; } }
@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
從結果中,能夠看到,總耗時是大於queryUserInfo
和queryUserAddress
之和的。但這兩個服務邏輯上並不存在前後關係,理論上最長耗時取決於最慢的那個,即queryUserAddress
異步
下例使用了FutureTask
,來異步調用queryUserInfo
和queryUserAddress
。ide
@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須要包含如下幾點:學習
一、範型 二、構造函數,傳入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
長,我猜想是咱們本身寫的未作更多的檢查和判斷。咱們本身寫的只是用來學習FutureTask
。this
不使用異步的方式時,queryUserAddress
在queryUserInfo
執行以後纔會執行,二者相加的時間算入總調用耗時。若是使用了異步線程調用,因爲queryUserAddress
耗時長,這樣在queryUserAddress
執行結束前,queryUserInfo
就執行結束了,這樣queryUserInfo
調用耗時就不計了。
原文出處:https://www.cnblogs.com/flylinran/p/10171449.html