Upload
koji-lin
View
1.533
Download
4
Embed Size (px)
Citation preview
#JCConf
使用 Java 的 Future/Promise API 來撰寫非同步程式
Koji LinJCConf 2015
#JCConf
使用 Java 的 Future/Promise API 來撰寫非同步非阻塞程式
Koji LinJCConf 2015
About me• Koji Lin
• @kojilin
• LINE Fukuoka, Japan
• 之前在 Cubie Inc.
• Android & iOS & 一些 Server side
大綱
• 非同步和非阻塞
• Java 8 前的方法
• j.u.c.CompletableFuture
什麼是非同步
• 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
什麼是非阻塞
• 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
為什麼?
• 有些工作很花時間
• 檔案讀寫
• 網路
• 加解密
• 圖片處理
壞處
• Web 服務,因為執行緒被長時間佔住,
提供的服務能處理的量降低
• 桌面或手機的應用,佔住執行緒會卡住
介面的反應,用戶體驗不佳
// 10 seconds
Image image1 = download(...);
render(image1);
// 12 seconds
Image image2 = download(...);
render(image2);
為什麼?
• 有些工作很花時間
• 想要更簡單的方式來控制流
程
Thread• java.lang.Thread
• JDK 1.0
void downloadAsync(String url,
Consumer<Image> c) {
new Thread(() -> {
Image result = download(...);
c.accept(result);
}).start();
}
Thread 的缺點
• 常需配合用 synchronized, wait, notify
和 join
• 不同 Thread 間如何存取同一個變數
• 如何控管?
• 如何組合相依的工作
fetchDataAsync(data -> {
downloadAsync(data.avatar, image -> {
render(image);
});
});
new Thread(() -> {
final Data result = fetchData(...);
Image image = download(data.avatar);
Bitmap bitmap = decode(image);
...
}).start();
不易組合和再利用
• 組合各種非同步方法,寫起來會變成
callback hell
• 包一個外層的 Thread 執行
• 忘記包的話?
• 如何控制資源?
更複雜的組合
• 如果想要兩個任務結果都執行完畢
• 利用 Thread#join
• 如果只要任一個先取得該怎麼做?
• Thread#join(long millis) 和檢查結果
值
• 浪費一個 Thread 一直去做檢查
while(true) {
t1.join(1000);
if(t1Value != null) {
return t1Value;
}
t2.join(1000);
...
}
而且我們也不想再直接使用 Thread API
Future• java.util.concurrent.Future
• Java SE 5.0
• 一個等待結果的容器,讓我們可以需
要時嘗試去取得結果
ExecutorService service =
Executors.newCachedThreadPool();
Future<Image> f =
service.submit(() ->
downloadImage(...);
);
Future<Image> f = download(...);
Future<Image> f = download(...);
Future<Image> f = download(...);
... // 做點其他事情
Future<Image> f = download(...);
... // 做點其他事情
Image result = f.get();// 取得結果
阻塞
// 如果還沒有結果,會停住直到有結果
future.get();
阻塞
// 如果還沒有結果,會停住直到有結果
future.get();
future.get(5,
TimeUnit.SECONDS);
例外處理
try {
renderImage(future.get());
} catch(ExecutionException e) {
...
e.getCause();// 會是執行時丟的錯誤
}
其他方便的方法
future.cancel(boolean);
future.isCancelled();
future.isDone();
Future• 從傳 callback 的方式,變成外部可以
自行再做處理
• 簡單易懂
• 只有 5 個方法
• 阻塞式的 API 來取得回傳
• 不易組合再利用
更複雜的組合
• 如果想要兩個任務結果都執行完畢
• 利用 Future#get
• 如果只要任一個先取得該怎麼做?
• Future#get(long, TimeUnit) 和檢查
結果值
• 浪費一個 Thread 一直去做檢查
CompletableFuture• java.util.concurrent
• Java SE 8
• implements Future, CompletionStage
CF<String> cf = CompletableFuture
.completableFuture("Value");
CF<String> cf = CompletableFuture
.completableFuture("Value");
String result = cf.get();
CF<String> cf = CompletableFuture
.completableFuture("Value");
String result = cf.join();
CF<String> cf = CompletableFuture
.completableFuture("Value");
cf.thenAccept(
s -> System.out.println(s)
);
String load(){...}
...
CF<String> cf = CompletableFuture
.supplyAsync(() -> load());
CF<String> cf = CompletableFuture
.supplyAsync(() -> load(),
executorService);
CF<String> cf = ...;
CF<Integer> length = cf.thenApply(
data -> data.length()
);
cf.thenApplyAsync(
data -> data.length()
);
cf.thenApplyAsync(
data -> data.length(),
executorService
);
cf1 = cf.thenApplyAsync(...);
cf2 = cf.thenApplyAsync(...);
Task1Task3
Task2
CF<String> cf = new
CompletableFuture();
cf.thenAccept(
s -> System.out.println(s)
);
CF<String> cf = new
CompletableFuture();
executor.submit(() -> {
String result = load();
cf.complete(result);
});
executor.submit(() -> {
try {
String result = load();
cf.complete(result);
} catch(Exception e) {
cf.completeExceptionally(e);
}
});
cf.whenComplete(
(String s, Throwable t) -> {
if(s != null)
System.out.println(s)
else
System.err.println(t)
}
);
How to change existing code
CF<User> findUser(long uid){...};
CF<Image> download(User user){...};
CF<?> cf =
findUser(12L).thenApply(user ->
download(user)
);
CF<CF<Image>> cf =
findUser(12L).thenApply(user ->
download(user)
);
CF<File> cf = findUser(12L)
.thenCompose(
user -> download(user)
);
CF<File> cf = findUser(12L)
.thenCompose(
user -> download(user)
)
.thenCompose(
img -> save(img)
);
findUser(12L)
.thenApply(...)
.thenApply(...)// exception
.thenCompose(...)
.whenComplete(...);
allOfCF<String> api1 = ...;
CF<String> api2 = ...;
CF<String> api3 = ...;
CF<Void> all =
CompletableFuture.allOf(api1,
api2, api3);
CF<List<String>> result =
all.thenApply(v ->
Arrays.asList(api1.get(),
api2.get(),
api3.get())
);
anyOfCF<String> api1 = ...;
CF<String> api2 = ...;
CF<String> api3 = ...;
CF<Object> all =
CompletableFuture.anyOf(api1,
api2, api3);
App 中常見的行為
CF<User> findUser(String id);
CF<User> saveUSer(String id);
CF<Image> downloadAvatar(String id);
findUser(...)
.thenCompose(user ->
saveUser(user.id))
.thenCompose(user ->
downloadAvatar(user.id))
.thenAccept(img ->
render(img));
同時多 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>>
CF<Void> all =
CF.allOf(queries.toArray());
CF<List<Value>> result =
all.thenApply(v ->
queries.stream()
.map(q -> q.join())
.collect(toList)
);
getOrderFromNetwork
getOrderFromDb
listProducts
getShipInfo
Data FlowsendMail
• 事件驅動 (event driven)
• 容易組合 (easy to compose)
• 控制權可以回給呼叫者
• 減少 thread 的浪費
優點
• Future/Promise 的混合
• 不少語言實作是分開的
• 爆多的方法數量
• 60+
缺點
Demo
• CompletableFuture#cancel
• 不能取消正在執行的工作
• 盡量使用 Async 語尾的 API
注意
findUserlistArticles
listFriend
支援非同步的 WEB 框架
• Servlet 3.0 AsyncContext
• Spring Framework
• Controller 的回傳直接用 CompletableFuture
• Play Framework
• Asynchronous web framework
• play.libs.F.Promise
Web application• 該不該用處理 http 的 thread 做事?
• Tomcat 有 max-threads 設定
• Play 本來就是 http 跟 worker 分離
• 每個要求的工作時間不一定相同
• 花多少時間?佔多少比例?
• 花時間的工作有沒有資源存取上限?
Simple Test• Job: 1500ms ~ 30%, 100ms ~ 70%
• 無處理上限
• Tomcat max-threads 200
• ab -n 1000 -c 400
• Async ~375 requests/second
• Sync ~300 requests/second
Simple Test• Job: 1500ms ~ 50%, 100ms ~ 50%
• 一次只能處理 30 件 1500 ms 的工作
• Async ~36 requests/second
• 50% < 2000ms
• Sync ~39 requests/second
• 50% < 5900ms
Android 不支援 Java 8 API
• Guava
• ListenableFuture
• RxJava
• Reactive Programming
• Bolts-Android
• Lambda !?
Reactive Programming• Data flow
• Propagation of change• Flow in Java 9 ?
Q&A