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
Yuichi.Sakuraba
June 29, 2023
Technology
9
3.8k
Javaの並列/並行処理の基本
2023.06.29
JJUG Java仕様勉強会資料
Yuichi.Sakuraba
June 29, 2023
Tweet
Share
More Decks by Yuichi.Sakuraba
See All by Yuichi.Sakuraba
で、ValhallaのValue Classってどうなったの?
skrb
2
8k
Javaにおける関数型プログラミンへの取り組み
skrb
7
400
今こそ、ラムダ式を考える - なぜあなたはラムダ式を苦手と感じるのか
skrb
6
21k
今こそ、ラムダ式を考える - ラムダ式はどうやって動くのか
skrb
7
11k
Project Amberで変わる Javaのプログラミングスタイル
skrb
3
950
String Templateによる文字列補間
skrb
4
3.6k
Virtual Threadの動作と効果的な使い方
skrb
2
580
JVMLSに参加してきた
skrb
1
2k
Who's Who in Java
skrb
3
20k
Other Decks in Technology
See All in Technology
When Windows Meets Kubernetes…
pichuang
0
300
Oracle Base Database Service:サービス概要のご紹介
oracle4engineer
PRO
1
16k
AWSサービスアップデート 2024/12 Part3
nrinetcom
PRO
0
140
商品レコメンドでのexplicit negative feedbackの活用
alpicola
1
350
20250116_自部署内でAmazon Nova体験会をやってみた話
riz3f7
1
100
My small contributions - Fujiwara Tech Conference 2025
ijin
0
1.4k
あなたの知らないクラフトビールの世界
miura55
0
120
Azureの開発で辛いところ
re3turn
0
240
embedパッケージを深掘りする / Deep Dive into embed Package in Go
task4233
1
210
自社 200 記事を元に整理した読みやすいテックブログを書くための Tips 集
masakihirose
2
330
Copilotの力を実感!3ヶ月間の生成AI研修の試行錯誤&成功事例をご紹介。果たして得たものとは・・?
ktc_shiori
0
350
JAWS-UG20250116_iOSアプリエンジニアがAWSreInventに行ってきた(真面目編)
totokit4
0
140
Featured
See All Featured
BBQ
matthewcrist
85
9.4k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
192
16k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
7k
Reflections from 52 weeks, 52 projects
jeffersonlam
348
20k
Six Lessons from altMBA
skipperchong
27
3.6k
Building Applications with DynamoDB
mza
93
6.2k
Visualization
eitanlees
146
15k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
Designing for humans not robots
tammielis
250
25k
Rebuilding a faster, lazier Slack
samanthasiow
79
8.8k
Fireside Chat
paigeccino
34
3.1k
Build your cross-platform service in a week with App Engine
jlugia
229
18k
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 櫻庭 祐一