スレッドと並行処理
🐱 この章の目次
GIL のない世界
Python の GIL(Global Interpreter Lock) は、CPU バウンドな処理の並列実行を制限します。 JVM には GIL が存在せず、複数のスレッドが複数の CPU コアで同時に実行されます。 これが JVM サービスが高い並行性能を発揮できる根本的な理由です。
スレッドプールと ExecutorService
JVM では スレッドプール を使ってスレッドを効率的に再利用します。
ExecutorService はタスクをスレッドプールに投入し、結果を非同期に取得する標準 API です。
// Java: スレッドプールでタスクを並列実行
ExecutorService executor = Executors.newFixedThreadPool(4);
Future<String> future = executor.submit(() -> {
// CPU バウンドな処理も並列に動く
return heavyComputation();
});
String result = future.get(); // 結果を待つ
executor.shutdown();
# Python: concurrent.futures(GIL によりCPU並列は不可)
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=4) as executor:
future = executor.submit(heavy_computation)
result = future.result() # I/O には有効だが CPU 並列は GIL で制限
Kotlin コルーチン
Kotlin コルーチン は Python の asyncio に似た構造化された並行処理モデルです。
ただし、バックグラウンドで実際のスレッドプールを使うため、CPU バウンドな処理も並列に実行できます。
// Kotlin: 構造化された並行処理
import kotlinx.coroutines.*
suspend fun fetchData(): String = withContext(Dispatchers.IO) {
// I/O 処理を別スレッドで実行
httpClient.get("https://api.example.com/data")
}
fun main() = runBlocking {
val results = listOf("url1", "url2", "url3").map { url ->
async { fetchData() } // 並行に実行
}.awaitAll()
}
# Python: asyncio(I/O 並行のみ、シングルスレッド)
import asyncio
async def fetch_data(url: str) -> str:
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
return await resp.text()
async def main():
results = await asyncio.gather(
fetch_data("url1"), fetch_data("url2"), fetch_data("url3")
)
Virtual Threads(Project Loom)
JDK 21 で導入された Virtual Threads(仮想スレッド) は、OS スレッドの上に軽量スレッドを実現します。
Python の asyncio のように大量の I/O 並行処理を扱えますが、既存のブロッキングコードをそのまま使えます。
async/await への書き換えが不要なため、移行コストがほぼゼロです。
// Java 21: Virtual Threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 100万スレッドも軽量に起動可能
IntStream.range(0, 1_000_000).forEach(i ->
executor.submit(() -> blockingHttpCall(i))
);
}
Python との比較まとめ
| Python | JVM | |
|---|---|---|
| CPU 並列 | multiprocessing(プロセス分離) | スレッドで直接並列 |
| I/O 並行 | asyncio(async/await 必須) | スレッド or Virtual Threads |
| 軽量タスク | コルーチン(シングルスレッド) | Kotlin コルーチン(マルチスレッド) |
| GIL | あり | なし |
JVM サービスがより多くの同時接続をシンプルなコードで処理できるのは、真のスレッド並列と Virtual Threads の組み合わせによるものです。