使用 java 上的 future/promise api

73
#JCConf 使用 Java Future/Promise API 來撰寫非同步程式 Koji Lin JCConf 2015

Upload: koji-lin

Post on 16-Apr-2017

1.534 views

Category:

Technology


4 download

TRANSCRIPT

Page 1: 使用 Java 上的 future/promise  API

#JCConf

使用 Java 的 Future/Promise API 來撰寫非同步程式

Koji LinJCConf 2015

Page 2: 使用 Java 上的 future/promise  API

#JCConf

使用 Java 的 Future/Promise API 來撰寫非同步非阻塞程式

Koji LinJCConf 2015

Page 3: 使用 Java 上的 future/promise  API

About me• Koji Lin

• @kojilin

• LINE Fukuoka, Japan

• 之前在 Cubie Inc.

• Android & iOS & 一些 Server side

Page 4: 使用 Java 上的 future/promise  API

大綱

• 非同步和非阻塞

• Java 8 前的方法

• j.u.c.CompletableFuture

Page 5: 使用 Java 上的 future/promise  API

什麼是非同步

• Asynchronous

• Not synchronous, or not

guaranteed to happen in the order

in which it appears in the code

from Playframework: Async, Reactive, Threads, Futures, ExecutionContexts

https://gist.github.com/sadache/4714280

Page 6: 使用 Java 上的 future/promise  API

什麼是非阻塞

• Non-blocking

• Doesn't cause the caller (Thread) to

be blocked waiting for a result,

from Playframework: Async, Reactive, Threads, Futures, ExecutionContexts

https://gist.github.com/sadache/4714280

Page 7: 使用 Java 上的 future/promise  API

為什麼?

• 有些工作很花時間

• 檔案讀寫

• 網路

• 加解密

• 圖片處理

Page 8: 使用 Java 上的 future/promise  API

壞處

• Web 服務,因為執行緒被長時間佔住,

提供的服務能處理的量降低

• 桌面或手機的應用,佔住執行緒會卡住

介面的反應,用戶體驗不佳

Page 9: 使用 Java 上的 future/promise  API

// 10 seconds

Image image1 = download(...);

render(image1);

// 12 seconds

Image image2 = download(...);

render(image2);

Page 10: 使用 Java 上的 future/promise  API

為什麼?

• 有些工作很花時間

• 想要更簡單的方式來控制流

Page 11: 使用 Java 上的 future/promise  API

Thread• java.lang.Thread

• JDK 1.0

Page 12: 使用 Java 上的 future/promise  API

void downloadAsync(String url,

Consumer<Image> c) {

new Thread(() -> {

Image result = download(...);

c.accept(result);

}).start();

}

Page 13: 使用 Java 上的 future/promise  API

Thread 的缺點

• 常需配合用 synchronized, wait, notify

和 join

• 不同 Thread 間如何存取同一個變數

• 如何控管?

• 如何組合相依的工作

Page 14: 使用 Java 上的 future/promise  API

fetchDataAsync(data -> {

downloadAsync(data.avatar, image -> {

render(image);

});

});

Page 15: 使用 Java 上的 future/promise  API

new Thread(() -> {

final Data result = fetchData(...);

Image image = download(data.avatar);

Bitmap bitmap = decode(image);

...

}).start();

Page 16: 使用 Java 上的 future/promise  API

不易組合和再利用

• 組合各種非同步方法,寫起來會變成

callback hell

• 包一個外層的 Thread 執行

• 忘記包的話?

• 如何控制資源?

Page 17: 使用 Java 上的 future/promise  API

更複雜的組合

• 如果想要兩個任務結果都執行完畢

• 利用 Thread#join

• 如果只要任一個先取得該怎麼做?

• Thread#join(long millis) 和檢查結果

• 浪費一個 Thread 一直去做檢查

Page 18: 使用 Java 上的 future/promise  API

while(true) {

t1.join(1000);

if(t1Value != null) {

return t1Value;

}

t2.join(1000);

...

}

Page 19: 使用 Java 上的 future/promise  API

而且我們也不想再直接使用 Thread API

Page 20: 使用 Java 上的 future/promise  API

Future• java.util.concurrent.Future

• Java SE 5.0

• 一個等待結果的容器,讓我們可以需

要時嘗試去取得結果

Page 21: 使用 Java 上的 future/promise  API

ExecutorService service =

Executors.newCachedThreadPool();

Future<Image> f =

service.submit(() ->

downloadImage(...);

);

Page 22: 使用 Java 上的 future/promise  API

Future<Image> f = download(...);

Page 23: 使用 Java 上的 future/promise  API

Future<Image> f = download(...);

Page 24: 使用 Java 上的 future/promise  API

Future<Image> f = download(...);

... // 做點其他事情

Page 25: 使用 Java 上的 future/promise  API

Future<Image> f = download(...);

... // 做點其他事情

Image result = f.get();// 取得結果

Page 26: 使用 Java 上的 future/promise  API

阻塞

// 如果還沒有結果,會停住直到有結果

future.get();

Page 27: 使用 Java 上的 future/promise  API

阻塞

// 如果還沒有結果,會停住直到有結果

future.get();

future.get(5,

TimeUnit.SECONDS);

Page 28: 使用 Java 上的 future/promise  API

例外處理

try {

renderImage(future.get());

} catch(ExecutionException e) {

...

e.getCause();// 會是執行時丟的錯誤

}

Page 29: 使用 Java 上的 future/promise  API

其他方便的方法

future.cancel(boolean);

future.isCancelled();

future.isDone();

Page 30: 使用 Java 上的 future/promise  API

Future• 從傳 callback 的方式,變成外部可以

自行再做處理

• 簡單易懂

• 只有 5 個方法

• 阻塞式的 API 來取得回傳

• 不易組合再利用

Page 31: 使用 Java 上的 future/promise  API

更複雜的組合

• 如果想要兩個任務結果都執行完畢

• 利用 Future#get

• 如果只要任一個先取得該怎麼做?

• Future#get(long, TimeUnit) 和檢查

結果值

• 浪費一個 Thread 一直去做檢查

Page 32: 使用 Java 上的 future/promise  API

CompletableFuture• java.util.concurrent

• Java SE 8

• implements Future, CompletionStage

Page 33: 使用 Java 上的 future/promise  API

CF<String> cf = CompletableFuture

.completableFuture("Value");

Page 34: 使用 Java 上的 future/promise  API

CF<String> cf = CompletableFuture

.completableFuture("Value");

String result = cf.get();

Page 35: 使用 Java 上的 future/promise  API

CF<String> cf = CompletableFuture

.completableFuture("Value");

String result = cf.join();

Page 36: 使用 Java 上的 future/promise  API

CF<String> cf = CompletableFuture

.completableFuture("Value");

cf.thenAccept(

s -> System.out.println(s)

);

Page 37: 使用 Java 上的 future/promise  API

String load(){...}

...

CF<String> cf = CompletableFuture

.supplyAsync(() -> load());

Page 38: 使用 Java 上的 future/promise  API

CF<String> cf = CompletableFuture

.supplyAsync(() -> load(),

executorService);

Page 39: 使用 Java 上的 future/promise  API

CF<String> cf = ...;

CF<Integer> length = cf.thenApply(

data -> data.length()

);

Page 40: 使用 Java 上的 future/promise  API

cf.thenApplyAsync(

data -> data.length()

);

Page 41: 使用 Java 上的 future/promise  API

cf.thenApplyAsync(

data -> data.length(),

executorService

);

Page 42: 使用 Java 上的 future/promise  API

cf1 = cf.thenApplyAsync(...);

cf2 = cf.thenApplyAsync(...);

Task1Task3

Task2

Page 43: 使用 Java 上的 future/promise  API

CF<String> cf = new

CompletableFuture();

cf.thenAccept(

s -> System.out.println(s)

);

Page 44: 使用 Java 上的 future/promise  API

CF<String> cf = new

CompletableFuture();

executor.submit(() -> {

String result = load();

cf.complete(result);

});

Page 45: 使用 Java 上的 future/promise  API

executor.submit(() -> {

try {

String result = load();

cf.complete(result);

} catch(Exception e) {

cf.completeExceptionally(e);

}

});

Page 46: 使用 Java 上的 future/promise  API

cf.whenComplete(

(String s, Throwable t) -> {

if(s != null)

System.out.println(s)

else

System.err.println(t)

}

);

Page 47: 使用 Java 上的 future/promise  API

How to change existing code

Page 48: 使用 Java 上的 future/promise  API

CF<User> findUser(long uid){...};

CF<Image> download(User user){...};

Page 49: 使用 Java 上的 future/promise  API

CF<?> cf =

findUser(12L).thenApply(user ->

download(user)

);

Page 50: 使用 Java 上的 future/promise  API

CF<CF<Image>> cf =

findUser(12L).thenApply(user ->

download(user)

);

Page 51: 使用 Java 上的 future/promise  API

CF<File> cf = findUser(12L)

.thenCompose(

user -> download(user)

);

Page 52: 使用 Java 上的 future/promise  API

CF<File> cf = findUser(12L)

.thenCompose(

user -> download(user)

)

.thenCompose(

img -> save(img)

);

Page 53: 使用 Java 上的 future/promise  API

findUser(12L)

.thenApply(...)

.thenApply(...)// exception

.thenCompose(...)

.whenComplete(...);

Page 54: 使用 Java 上的 future/promise  API

allOfCF<String> api1 = ...;

CF<String> api2 = ...;

CF<String> api3 = ...;

CF<Void> all =

CompletableFuture.allOf(api1,

api2, api3);

Page 55: 使用 Java 上的 future/promise  API

CF<List<String>> result =

all.thenApply(v ->

Arrays.asList(api1.get(),

api2.get(),

api3.get())

);

Page 56: 使用 Java 上的 future/promise  API

anyOfCF<String> api1 = ...;

CF<String> api2 = ...;

CF<String> api3 = ...;

CF<Object> all =

CompletableFuture.anyOf(api1,

api2, api3);

Page 57: 使用 Java 上的 future/promise  API

App 中常見的行為

CF<User> findUser(String id);

CF<User> saveUSer(String id);

CF<Image> downloadAvatar(String id);

Page 58: 使用 Java 上的 future/promise  API

findUser(...)

.thenCompose(user ->

saveUser(user.id))

.thenCompose(user ->

downloadAvatar(user.id))

.thenAccept(img ->

render(img));

Page 59: 使用 Java 上的 future/promise  API

同時多 key 查詢CF<Value> executeQuery(String id);

List<CF<Value>> queries =

ids.stream()

.map(id -> executeQuery(id))

.collect(toList());

//using allOf to let

//List<CF<Value>> -> CF<List<Value>>

Page 60: 使用 Java 上的 future/promise  API

CF<Void> all =

CF.allOf(queries.toArray());

Page 61: 使用 Java 上的 future/promise  API

CF<List<Value>> result =

all.thenApply(v ->

queries.stream()

.map(q -> q.join())

.collect(toList)

);

Page 62: 使用 Java 上的 future/promise  API

getOrderFromNetwork

getOrderFromDb

listProducts

getShipInfo

Data FlowsendMail

Page 63: 使用 Java 上的 future/promise  API

• 事件驅動 (event driven)

• 容易組合 (easy to compose)

• 控制權可以回給呼叫者

• 減少 thread 的浪費

優點

Page 64: 使用 Java 上的 future/promise  API

• Future/Promise 的混合

• 不少語言實作是分開的

• 爆多的方法數量

• 60+

缺點

Page 65: 使用 Java 上的 future/promise  API

Demo

Page 66: 使用 Java 上的 future/promise  API

• CompletableFuture#cancel

• 不能取消正在執行的工作

• 盡量使用 Async 語尾的 API

注意

findUserlistArticles

listFriend

Page 67: 使用 Java 上的 future/promise  API

支援非同步的 WEB 框架

• Servlet 3.0 AsyncContext

• Spring Framework

• Controller 的回傳直接用 CompletableFuture

• Play Framework

• Asynchronous web framework

• play.libs.F.Promise

Page 68: 使用 Java 上的 future/promise  API

Web application• 該不該用處理 http 的 thread 做事?

• Tomcat 有 max-threads 設定

• Play 本來就是 http 跟 worker 分離

• 每個要求的工作時間不一定相同

• 花多少時間?佔多少比例?

• 花時間的工作有沒有資源存取上限?

Page 69: 使用 Java 上的 future/promise  API

Simple Test• Job: 1500ms ~ 30%, 100ms ~ 70%

• 無處理上限

• Tomcat max-threads 200

• ab -n 1000 -c 400

• Async ~375 requests/second

• Sync ~300 requests/second

Page 70: 使用 Java 上的 future/promise  API

Simple Test• Job: 1500ms ~ 50%, 100ms ~ 50%

• 一次只能處理 30 件 1500 ms 的工作

• Async ~36 requests/second

• 50% < 2000ms

• Sync ~39 requests/second

• 50% < 5900ms

Page 71: 使用 Java 上的 future/promise  API

Android 不支援 Java 8 API

• Guava

• ListenableFuture

• RxJava

• Reactive Programming

• Bolts-Android

• Lambda !?

Page 72: 使用 Java 上的 future/promise  API

Reactive Programming• Data flow

• Propagation of change• Flow in Java 9 ?

Page 73: 使用 Java 上的 future/promise  API

Q&A