# 2023 java conference ## 2023 java conference 聽講順序 09:25–09:35 報到 ### 演講 | 時間 | 地點 | 主題 | | -------- | -------- | -------- | | 09:40 ~ 10:25 | 401 | 深入淺出 Java 21 功能 | | 10:40 ~ 11:25 | 402CD | Redis 快取設計漫談 | | 11:40 ~ 12:25 | 401 | 🇬🇧 🎞️ Jakarta EE and MicroProfile: Open Cloud-Native Java APIs for All| | 12:25 ~ 13:25 | | Lunch and Break| | 13:25 ~ 13:40 | 203 | 面試的內容(x)| | 13:45 ~ 14:00 | 303 | java串接line pay付款 (x) | | 14:10 ~ 14:55 | 203 | Create your library | | 15:05 ~ 15:50 | 402cd | http | 15:50 ~ 16:20 | |Tea Time| | 16:20 ~ 17:05 | 401 |Refactoring to Serverless| | 17:15 ~ 18:00 | 401 | Virtual Threads| ## 第一場 : 深入淺出介紹了jAVA 21 的新功能 參考 : https://cyberjos.blog/java/seminar/jcconf-2023-head-first-java-21/ ## 第二場 : Redis 快取設計漫談 ## Lunch and Break 吃便當 ## Tea Time ## 最後一場 : Virtual Threads 大綱分別是 ● Java 對應多執⾏緒的歷史 ● 為什麼要 Virtual Threads ● 如何使⽤ Virtual Threads ● ⼀些要注意的細節 ● Java 21 後 ## Virtual Threads 是甚麼呢 - ⼀種輕量級由 JVM 管理的執⾏緒 - 與既有的⼀對⼀對應 Platform Thread 不同 - 使⽤⽅式基本上跟現在的 Thread 沒有差異 ,原本使用 Thread API 的既有程式碼能在最小的 改動下採用虛擬執行緒 - • 繼承 Thread 的輕量級執行緒,可大幅減少撰 寫、維護和監控高吞吐並行程式的工作量 • 用 thread-per-request 風格撰寫的服務器應用 程式能以接近最佳化的硬體使用率來進行擴容 • 原本使用 Thread API 的既有程式碼能在最小的 改動下採用虛擬執行緒 • 與既有的 JDK 工具整合,使其易於使用、除錯 和量測 - 目的 : 為了因應現在後端⾼ Throughput 的情境 - 現在的後端開發很多都是⾼ I/O 的情況, - 使⽤⼀個執⾏緒對應⼀個請求變成⼀種浪費. ## Thread 的利⽤⽅法 ``` new Thread(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start(); ``` ## Thread 的利⽤⽅法 ``` Thread.ofPlatform().start(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); ``` ## Virtual Threads 的利⽤⽅法 ``` Thread.ofVirtual().start(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); ``` ## Thread 的利⽤⽅法 ``` ExecutorService executorService = Executors.newFixedThreadPool(16); public void foo(Request request) { executorService.submit(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); ... } ``` ## Virtual Threads 的利⽤⽅法 ``` ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor(); public void foo(Request request) { executorService.submit(() -> { try { Thread.sleep(1000L); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); ... } ``` ## Virtual Threads 的利用方法 - 通常後端來說,不太需要自己建立 Thread,只 要框架本身提供的處理要求的 Thread 為 Virtual Thread,那就可以用一個 request 一個 Thread 的方式寫跟程式邏輯 - 以前需要使用 Thread Pool 在背後執行的部分 ,可以利用 Executors 的 newVirtualThreadPerTaskExecutor # 記憶體的使用量 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 8_000).forEach(i -> executor.submit(() -> { Thread.sleep(Duration.ofSeconds(10)); return i; })); } // 補圖 Thread (reserved=78389KB, committed=78389KB) //使用一班的 try (var executor = Executors.newCachedThreadPool()) { IntStream.range(0, 8_000).forEach(i -> executor.submit(() -> { Thread.sleep(Duration.ofSeconds(10)); return i; })); } // 補圖 Thread (reserved=16561256KB, committed=16561256KB) ## Thread 的後端開發 最早典型的後端會有 Thread Pool,對每個存取 一對一處理  多數後端開發都會有存取資料庫或其他外部API後 等待I/O  同時間來的大量的請求時,會利用到多個執行緒  超過預期的請求,只能讓它們等或是開更多執行緒  ## 後端程式常有的情況 程式中業務邏輯在整個要求中佔的比例不高 ○ 資料庫存取約10ms ○ 存取其他後端約10~100ms ○ 實際上在取得資料後的處理花費非常小,所 以一個執行緒基本上只會佔用一點 CPU 處 理邏輯 ## 一請求對應一執行緒的問題 典型的環境我們通常會開幾百到幾千執行緒也 不會遇到太大問題 ○ Platform Thread 大概需要 1~2MB 左右, 建立大概要 milliseconds 等級 ● 沒有限制執行緒而請求增加到達一個程度 ○ java.lang.OutOfMemory ○ Context switch 的成本 ● 多數 Server 會使用 Thread Pool 限制上限 ○ 超過預期的要求會產生大量的 Timeout ## 多數 Server 會使用 Thread Pool 限制上限 ○ 超過預期的要求會產生大量的 Timeout 為了讓 CPU 不閒置,使用像 CompletableFuture、WebFlux 以及 RxJava 之類的函示庫,搭配非同步 I/O 及少量的 執行緒來改進效能 原本的指令式程式碼 List<Item> getRanking(String country) { String rankingType = api.getRankingType("JP"); List<String> rankingIds = searchClient.getRanking(rankingType); return rankingIds.stream() .map(dao::getItem) .collect(Collectors.toList()); } 利用 CompletableFuture CompletableFuture<List<Item>> getRanking(String country) { return api.getRankingType("JP").thenCompose(rankingType -> { return searchClient.getRanking(rankingType); }).thenCompose(rankingIds -> { final List<CompletableFuture<Item>> itemFutures = rankingIds.stream().map(dao::getItem).toList(); return CompletableFuture .allOf(itemFutures.toArray( new CompletableFuture[0])) .thenApply(ignored -> itemFutures.stream().map(CompletableFuture::join) .toList()); }); 非同步程式的問題 ● 一使用類似模式,所有方法基本上都得利用一 樣模式撰寫 ● 程式丟出 Exception 時, stack trace 只有執行 時的 Thread 資訊,非常難除錯 ● 無法利用簡單的 try catch 錯誤處理 Stack trace 示意  ## Virtual Threads 如果執行緒能變得非常輕量?就能像以前一樣 用指令式的方式撰寫 ○ 錯誤處理和除錯都變得容易 ○ 提高 Throughput ● 多輕量?有辦法建立幾十到百萬以上的執行緒 ○ 數百 Bytes 的 metadata ○ Stack 的資料放在 Heap ○ Context switch 快(ns 等級) 建立 Virtual Thread 不會馬上佔用 Platform Threadfi›  Virtual Threads 可以透過 -Djdk.virtualThreadScheduler.parallelism 設定 carrier thread 的數量,預設為 processor 的數量 List<Item> getRanking(String country) { String rankingType = api.getRankingType("JP"); List<String> rankingIds = searchClient.getRanking(rankingType); return rankingIds.stream() .map(dao::getItem) .collect(Collectors.toList()); } 一些要注意的地方 ## 對 Virtual Thread 的誤解 ● 轉換成 Virtual Thread 並不會變更快 ○ 導入的理由是為了更高的 Throughput ○ 對於被執行的 Task,帶來的 overhead 是變 多的 ○ 幾乎只有 CPU 運算為主的工作,就可能不 適合利用它 ● 不要重複利用 Virtual Thread,一個 Task 就是 一個 Thread > Executors#newVirtualThreadPerTaskExecutor ## Thread pinning ● 以下狀況下 Virtual Threads 無法在 blocking 時 unmount ○ 執行 synchronized 方法和區塊 ○ 執行 native ● 盡量避免使用 synchronized ○ 如果 synchronized 的區塊不多且佔用時間 不⻑,可以測量看看不一定要急著改 ○ 需要的話可以利用如 java.util.concurrent.lock.ReentrantLock ## Thread pinning 可以利用 JFR 或是 -Djdk.tracePinnedThreads=full 來找出有問題 的地方 ## ThreadLocal ThreadLocal 通常用在兩種狀況 ○ 用來傳遞 context 物件 ■ 例如 Spring 的 transaction 和 RequestContextHolder ○ 當作較重的資源的 cached/pool ■ 例如 Jackson、OpenTelemetry ■ 如果有幾十幾百萬執行緒,那會用到多少 資源? ## Java 21 釋出之後 ● 有幾個相關的 JEP 還在 Preview 中 ○ JEP 446: Scoped Values ○ JEP 453: Structured Concurrency ● 要能完整支援,記得要確認 Web framework、 database driver、HTTP client、message system 等等 ○ Spring 6 & SpringBoot 3.2 Scoped Values ## 提供比 ThreadLocal 輕量且安全的 API ○ 傳遞像 context 的物件 static ScopedValue<Context> CONTEXT = ScopedValue.newInstance(); void serve(Request request, Response response) { var context = createContext(request); ScopedValue.where(CONTEXT, context) .run(() -> handle(request, response)); } private void handle(Request request, Response response) { var context = CONTEXT.get(); } ## Structured Concurrency 能把多個在不同執行緒處理的工作當作一個單位 ○ 簡化錯誤處理,取消跟改善不易除錯的問題 Response handle() throws Exception { try(var scope = new StructuredTaskScope.ShutdownOnFailure()) { Supplier<String> user = scope.fork(() -> findUser()); Supplier<Integer> order = scope.fork(() -> fetchOrder()); scope.join().throwIfFailed(); return new Response(user.get(), order.get()); } } ## 改進 thread-per-request 效率 為了在擴展應用程式的同時能與平台維持協作, 所以用更有效率的方式來實作執行緒 • 如同 OS 將大量虛擬定址空間映射到有限實體 RAM 一樣,JVM 將大量虛擬執行緒映射到少 量的 OS 執行緒,達成增加執行緒數量 • 虛擬執行緒會被臨時對映到載體執行緒中,一 旦遇到阻塞操作,就會從載體中移除,而載體 可以執行另一個虛擬執行緒(新的或之前被阻 塞的) ## 總結 例如在高 I/O 的後端開發,Virtual Threads 讓 我們可以不用透過 Reactive 的方式來達到高 Throughput ● 不太建議現在馬上導入,記得要檢查看看主要 利用的框架跟函式庫有沒有問題 ○ 搭配一些工具來偵測有沒有 Thread pinning 或是預期外的資源被使用 ## 參考 : [淺淡 Virtual Thread - HackMD](https://hackmd.io/@QCZ_Kvv1ScixyAPzRYDKWQ/HJjRAQwCn) [JCConf 2023 - 深入淺出 Java 21 功能 投影片 | CyberJos is Blogging. 網路老喬的深聊漫談](https://cyberjos.blog/java/seminar/jcconf-2023-head-first-java-21/) https://docs.google.com/presentation/d/160DkeZv_px5PlolTP5B-ROYs7NVIyrSvTHP96I0yhTY/edit?usp=sharing
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up