Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Javaの並列/並行処理の基本
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Yuichi.Sakuraba
June 29, 2023
Technology
4.5k
9
Share
Javaの並列/並行処理の基本
2023.06.29
JJUG Java仕様勉強会資料
Yuichi.Sakuraba
June 29, 2023
More Decks by Yuichi.Sakuraba
See All by Yuichi.Sakuraba
Java 25に至る道
skrb
3
300
Lazy Constant - finalフィールドの遅延初期化
skrb
0
3.6k
Language Update: Java
skrb
2
470
Java 30周年記念! Javaの30年をふりかえる
skrb
4
3.8k
JavaにおけるNull非許容性
skrb
2
3.9k
あなたはJVMの気持ちを理解できるか?
skrb
6
32k
で、ValhallaのValue Classってどうなったの?
skrb
2
15k
Javaにおける関数型プログラミンへの取り組み
skrb
7
700
今こそ、ラムダ式を考える - なぜあなたはラムダ式を苦手と感じるのか
skrb
6
27k
Other Decks in Technology
See All in Technology
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
15
100k
業務に残された「良くない型」で考える「TypeScriptの難しさ」
sajikix
2
520
AsyncStreamでマルチブロードキャストを実装する
1mash0
1
150
20260515 ログイン機能だけではないアカウント管理を全体で考える~サービス設計者向け~
oidfj
1
810
R&D 祭 2024 UE5で絵コンテ・作画の制作支援ツールをつくる話
olmdrd
PRO
0
190
クラウドからエッジまで ~ 1,700台を支える監視設計~
optfit
0
110
Redmine次期バージョン7.0の注目新機能解説 — UI/UX強化と連携強化を中心に
vividtone
1
180
そのSLO 99.9%、本当に必要ですか? 〜優先度付きSLOによる責任共有の設計思想〜 / Is that 99.9% SLO really necessary? Design philosophy of shared responsibility through prioritized SLOs
vtryo
0
830
実践 TanStack Start ― 新規プロダクトを開発して確立した、サーバーとクライアント境界の設計パターン / Practical TanStack Start Server-Client Boundary Patterns
kaminashi
1
100
React Compiler導入から21ヶ月、いま始めるならこうやる
astatsuya
2
270
ワールドカフェ再び、そしてゴール・ルール・ロール・ツール / World Café Revisited, and the Goals-Rules-Roles-Tools
ks91
PRO
0
180
Oracle AI Database@Azure:サービス概要のご紹介
oracle4engineer
PRO
6
1.7k
Featured
See All Featured
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.2k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
350
A better future with KSS
kneath
240
18k
How to make the Groovebox
asonas
2
2.2k
A Guide to Academic Writing Using Generative AI - A Workshop
ks91
PRO
1
300
New Earth Scene 8
popppiees
3
2.2k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
370
Paper Plane (Part 1)
katiecoart
PRO
0
7.6k
エンジニアに許された特別な時間の終わり
watany
106
240k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.7k
Testing 201, or: Great Expectations
jmmastey
46
8.1k
The agentic SEO stack - context over prompts
schlessera
0
780
Transcript
Javaの並列/並行処理の基本 Java in the Box 櫻庭 祐一
Agenda Threadの基本 Concurrency Utilities 非同期タスクを記述する Concurrency Utilitiesの拡張 Folk/Join Framework CompletableFuture
Threadの基本
Thread 処理を並列/並行に処理するための最小単位 基本的にはOSスレッドのラッパー ただし、Virtual ThreadはJVMが管理するスレッド Threadに対する操作 OSスレッドに対する操作
Threadにおける2つの側面 ライフサイクルの管理 生成から廃棄まで スレッドスケジューリング スレッドの実行順序 実行中のスレッドの切り替え Context Switch
var thread = new Thread( new Runnable() { @Override public
void run() { // 処理 while (!condition) { Thread.yield(); } // 処理 } }); thread.start(); スレッド生成 非同期タスク スレッドを譲る ただし、本当に譲るかどうかはスケジューラしだい 非同期タスクが完了でスレッド廃棄 タスクの実行 ただし、実際の実行タイミングはスケジューラが決める
スレッドの生成 時間がかかる メモリ大量消費 勝手に野良スレッドを作成されると管理が難しい コンテキストスイッチ 時間がかかる メモリ大量消費 勝手にスレッド切替されるとスケジューリングできない 非同期処理の実行・管理はJVMにまかせる 開発者は非同期タスクの記述に集中する
Thread を直接使用することは もはや アンチパターン 特に以下のメソッドは使用しない • stop() • suspend() •
resume() これらのメソッドはJava 21から@Deprecated(forRemoval=true)
Concurrency Utilities
Concurrency Utilities 非同期タスクの実行・管理 並列コレクション アトミック操作 API ロック API
非同期タスクの実行・管理 ExecutorService タスクの実行管理 主にスレッドプール Executors ExecutorServiceのファクトリ Runnable/Callable 非同期タスク Future 非同期タスクの管理
Executors ExecutorServiceのファクトリ 用途に合わせてExecutorServiceオブジェクトを生成 newSingleThreadExecutor newFixedThreadPool newCachedThreadPool newScheduledThreadPool newWorkStealingPool newVirtualThreadPerTaskExecutor シングルスレッド動作のExecutorServce
スレッド数固定のスレッドプール 必要に応じてスレッド生成するスレッドプール 周期的タスクを実行するスレッドプール Work Stealingを使用するスレッドプール (Java 8) Virtual Threadを使用するExecutorService (Java 21)
ExecutorService 非同期タスクの実行 タスクの実行方法/スケジューリングは実装クラスに依存 主なメソッド submit close invokeAll/invokeAny shutdown/shutdownNow 非同期タスクの登録 戻り値はFuture<T>
AutoClosable (Java 19) 複数タスクの実行 今後Structured Concurrencyで置き換え Java 19以前に使用していたExecutorServiceの終了
Runnable/Callable 非同期タスクを記述 @FunctionalInterfaceなので、ラムダ式で記述 Runnable Callable<T> 引数なし 戻り値なしのタスク Checked Exceptionはスローできない RuntimeExceptionはUncaught
Exceptionとして扱われる 引数なし 戻り値ありのタスク Checked Exceptionをスローできる
Future<T> 非同期タスクの管理 型パラメータはタスクの戻り値型 Runnableの場合、Future<?> get cancel isDone/isCancelled resultNow exceptionNow state
結果の取得 タスクが完了するまでブロックする タスクのキャンセル (キャンセルできるかはタスクしだい) タスクの状態を調べる ブロックせずに結果取得 完了していなければ例外 (Java 19) ブロックせずに例外取得 例外で完了していなければ例外 (Java 19) タスクの状態を調べる 戻り値はFuture.State列挙型 (Java 19)
final var path = ... try (var pool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors())) { Future<List<String>> future = pool.submit(() -> { return Files.readAllLines(path); }); List<String> contents = future.get(); } catch (ExecutionException ex) { // タスク実行時の例外 } catch (InterruptedException ex) { // タスク実行中に割り込み発生 } 例: 非同期ファイル読み込み 引数でスレッド数指定 ここではCPUのコア数を指定 タスク登録 IOExceptionが発生した場合 ExecutionExceptionのcauseとなり Future.getメソッドでcatchできる getはタスク完了までブロックするため このままだと並行処理の意味がない
final var path = ... try (var pool = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors())) { Future<List<String>> future = pool.submit(() -> { return Files.readAllLines(path); }); /*たとえば*/ while(!future.isDone()) { /* タスク完了まで他の処理を実行 */ } List<String> contents = future.get(); } catch (ExecutionException ex) { // タスク実行時の例外 } catch (InterruptedException ex) { // タスク実行中に割り込み発生 } 例: 非同期ファイル読み込み 引数でスレッド数指定 ここではCPUのコア数を指定 タスク登録 IOExceptionが発生した場合 ExecutionExceptionのcauseとなり Future.getメソッドでcatchできる
非同期タスクを記述する
非同期タスクのポイント いわゆるスレッドセーフ 非同期実行でもデータを壊さない 実行タイミングによらず結果が同一 並行度と性能が比例する 並行処理のボトルネックが少 スケーラビリティ 安全性
安全性を損なう要因 競り合い状態 複数スレッドが同タイミングで書き込みを行うことで データが破壊される データ書き込みの非可視化 メモリ書き込みタイミングにより予測不可能な動作になる
競り合い状態の例 class Counter { private int count = 0; public
int getNext() { return count++; } } read 複製 加算 write Thread #1 read 複製 加算 write Thread #2
競り合い状態の例 class Counter { private int count = 0; public
int getNext() { return count++; } } read 複製 加算 write Thread #1 read 複製 加算 write Thread #2 複数スレッドからcountにアクセス可能 アクセスを単一スレッドに制限 = 同期化
競り合い状態の解消 class SyncCounter { private int count = 0; synchronized
public int getNext() { return count++; } } class LockedCounter { private ReentrantLock lock = new ReentrantLock(); private int count = 0; public int getNext() { lock.lock(); try { return count++; } finally { lock.unlock(); } } }
class OneDataContainer { private int value; synchronized public int getValue()
{ return value; } synchronized public void setValue(int v) { value = v; } } boolean update(int value) { if (container.getValue() == value) { return false; } else { container.setValue(value); return true; } } スレッドセーフ? Check Act Check-Then-Actは 処理全体を同期化する
class OneDataContainer { private int value; synchronized public int getValue()
{ return value; } synchronized public void setValue(int v) { value = v; } } synchronized boolean update(int value) { if (container.getValue() == value) { return false; } else { container.setValue(value); return true; } } スレッドセーフ? synchronizedメソッドは synchronized(this) { … } containerが逸脱している場合 スレッドセーフではない
class OneDataContainer { private int value; synchronized public int getValue()
{ return value; } synchronized public void setValue(int v) { value = v; } } boolean update(int value) { synchronized(container) { if (container.getValue() == value) { return false; } else { container.setValue(value); return true; }} } containerを同期化することで スレッドセーフになる
データ書き込みの非可視化の例 public class NoVisibility { private static boolean ready; private
static int number; public static void main(String... args) { try (var pool = Executors.newCachedThreadPool()){ pool.submit(() -> { while (!ready) { Thread.yield(); } System.out.println(number); }); number = 42; ready = true; } } } Java並行処理プログラミングp40より引用、改変 同期化により可視化を保証
同期化 同期化を行うことで • リソースへのアクセスを単一スレッドに制限 複数スレッドでも逐次動作になりボトルネック化 • データ書き込みの可視化を保証 メインメモリへのアクセスにより多大な時間を消費 同期化することでスケーラビリティが低下
安全性を担保しつつスケールさせるためには 同期化させる部分を最小限に 安易にsynchronizedメソッドを定義しない synchronizedよりもReentrantLockなどのロックAPIを使用 スレッド間でのリソースを共有しない リソースを共有するため競り合い状態などが発生する 引数、戻り値だけでスレッド間のやり取りを行う 状態を変更しない 状態が変更できるために競り合い状態などが発生する 状態を変更できないイミュータブル性を重視
Java 16で導入されたRecord型を活用
スケーラビリティ向上させるために タスクの独立性 タスク間のやり取りやリソース共有を排除する タスクの均質化 同じタスクの並行処理は効率的 同じタスクでなくても、なるべく同質な処理にする タスク粒度の最適化 タスクの粒度が大きいとタスクスケジューリングが難しい 計算処理であれば細粒度 (分割統治法)
Concurrency Utilitiesの拡張
Concurrency Utilitiesの拡張 Fork/Join Framework (Java 7) 大量の計算処理、データ処理に対する非同期処理 応答性の向上 CompletableFuture (Java
8) I/Oを含む業務ロジックの非同期処理 スループットの向上
Fork/Join Framework (Java 7) 大量の計算処理、データ処理を効率的に処理するフレームワーク キーとなる技術 分割統治法 Work-Stealingタスクスケジューリング Parallel Stream、Arrays.sortなどで使用
オーバーヘッドがあるため、データ数が多い場合のみ Arrays.sortの場合、デフォルトで4096以上 開発者が直接使うことはほぼないはず…
分割統治法 大きいタスクを処理しやすいサイズまで分割して処理 例: ソート 1 2 4 5 8 10
11 3 1 2 4 5 8 10 11 3 1 2 4 5 8 10 11 3 4 10 1 5 11 2 8 3 2 10 1 5 11 4 8 3 8 11 5 2 4 10 3 1
Work-Stealingタスクスケジューリング 個々のスレッドがタスクキューを持つ タスクキューにタスクあり 先頭から取り出して処理 タスクキューにタスクなし 他スレッドのタスクキューの末尾からタスクを取り出す(Steal) 分割したタスクはキューに積まれる 末尾に近いほどタスクが小さい 効率的にタスクを処理できる
Fork/Join FrameworkのAPI ForkJoinPool Work-Stealを使用したスレッドプール ForkJoinTask 分割統治法を使用してタスクを記述する基底クラス RecursiveTask/RecursiveAction 再帰的タスクを記述用クラス RecursiveActionは戻り値なし CountedCompleter
保留中のタスクがない場合に完了アクションを記述できるタスク
例: フィボナッチ数 F(n) = F(n-1) + F(n-2) class FibonacciTask extends
RecursiveTask<Integer> { private final int n; public FibonacciTask(int n) { this.n = n; } protected Integer compute() { if (n <= 1) return n; var f1 = new FibonacciTask(n - 1); f1.fork(); var f2 = new FibonacciTask(n - 2); return f2.compute() + f1.join(); }} ForkJoinPool fjPool = new ForkJoinPool(); var task = fjPool.submit(new FibonacciTask(30)); System.out.println(task.get()); タスク分割 タスク分割 タスクのフォーク タスクの完了を待って、結果を取得 タスク記述メソッド
CompletableFuture (Java 8) 非同期で行う一連の処理を関数で連ねる I/O処理を非同期で行うことでスループット向上 開始 (staticメソッド) completedFuture runAsync suplyAsync
一連のタスク記述メソッド thenAccept/Apply/Run(Async) 他に処理の合成、例外処理など多くのメソッドを提供 引数が次のラムダ式の引数になる タスクはRunnable タスクはSupplier 引数、戻り値の有無でラムダ式が決まる メソッドの最後がAsyncの場合、非同期に実行される
CompletableFuture.supplyAsync(() -> Path.of(...)) .thenApplyAsync(path -> { try { return Optional.of(Files.readAllLines(path));
} catch (IOException ex) { return Optional.<List<String>>empty(); } }) .thenAccept(opt -> { opt.ifPresent(contents -> { contents.forEach(System.out::println); }); }); 例: 非同期ファイル読み込み 引数あり、戻り値ありのタスクを非同期に実行 ファイル読み込みタスク完了後、実行
Conclusion Threadを直接使用するのはもはやアンチパターン Concurrency Utilitiesでスレッド管理とタスクを分離 安全性とスケーラビリティに注意してタスクを記述 応答性向上: Fork/Join Framework スループット向上: CompletableFuture
おまけ Javaで並列・並行処理を行うのであれば 絶版だけど… 電子版もないけど…
Javaの並列/並行処理の基本 Java in the Box 櫻庭 祐一