目次
KotlinのThreadは「独自実装」ではない
まず最重要ポイントです。
Kotlin(JVM)のスレッドは、Javaの java.lang.Thread をそのまま利用しています。
Kotlinはスレッド機構を独自に持っているわけではなく、
- JVM上では Java Thread
- Kotlin標準ライブラリは「書きやすくするためのラッパー」
という立ち位置です。
つまりKotlinでThreadを理解する=Java Threadの理解と同義です。
Threadの基本:start() と run() の決定的な違い
正しいスレッド起動
val thread = Thread {
println("別スレッドで実行")
}
thread.start()
start()を呼ぶ
→ JVMが新しいスレッドを生成し、run()が 別スレッド上で 実行される
よくある間違い
thread.run() // 新しいスレッドは作られない
run()は ただのメソッド- 呼び出し元スレッドで同期的に実行される
スレッドを作るのは start() の責務
この点はスレッド理解の基礎中の基礎です。
Kotlin標準の kotlin.concurrent.thread
Kotlinでは、Javaより簡潔にスレッドを書けるユーティリティが用意されています。
import kotlin.concurrent.thread
thread {
println("Kotlinらしいスレッド起動")
}
主なオプション
thread(
start = true,
isDaemon = false,
name = "WorkerThread"
) {
// 処理
}
| パラメータ | 説明 |
|---|---|
| start | すぐに実行するか |
| isDaemon | デーモンスレッドか |
| name | スレッド名(デバッグに有用) |
| priority | 優先度(OS/JVM依存で信頼性は低い) |
※ priority は指定可能ですが、実務では挙動が不安定なため依存しない設計が一般的です。
スレッドの状態(ライフサイクル)
JVMスレッドは以下の状態を持ちます。
- NEW
生成されたが未開始 - RUNNABLE
JVM上で実行可能な状態
※「実行中」と「実行待ち」の両方を含む - BLOCKED / WAITING / TIMED_WAITING
ロック待ち、待機中、時間付き待機 - TERMINATED
実行終了
thread {
Thread.sleep(1000)
println("1秒後に実行")
}
Thread.sleep()は 現在のスレッドを停止- UIスレッドで使うとフリーズの原因になるため注意
スレッドで最も危険な問題:共有データ
典型的なレースコンディション
var count = 0
thread {
repeat(1000) { count++ }
}
thread {
repeat(1000) { count++ }
}
一見すると count は 2000 になりそうですが、そうならないことがあります。
理由
count++は- 読み取り
- 加算
- 書き込み
の複合操作であり、原子的ではない
排他制御(synchronized)
synchronized ブロック
val lock = Any()
var count = 0
thread {
repeat(1000) {
synchronized(lock) {
count++
}
}
}
- JVMのモニタロックを利用
- Javaの
synchronizedと完全に同じ仕組み
@Synchronized アノテーション
@Synchronized
fun increment() {
count++
}
- メソッド全体をロック
- JVM専用
- Kotlin/Native では使えない
スレッド制御API
join():スレッドの終了待ち
val t = thread {
Thread.sleep(1000)
}
t.join()
println("処理完了")
- 非同期処理の完了待ちに必須
interrupt():強制停止ではない
val t = thread {
try {
Thread.sleep(5000)
} catch (e: InterruptedException) {
println("割り込み検知")
}
}
Thread.sleep(1000)
t.interrupt()
正確な挙動
interrupt()は
割り込み状態フラグを立てるsleep / wait / join中ならInterruptedExceptionが発生- 通常処理中なら
スレッド側がisInterrupted()を確認して協調的に終了する必要がある
強制停止ではない
協調的キャンセル
スレッドプールの重要性
なぜ直接Threadを大量に作らないのか?
- スレッド生成は高コスト
- メモリ消費(スタック)
- コンテキストスイッチ負荷
ExecutorService
val executor = Executors.newFixedThreadPool(4)
executor.execute {
println("スレッドプール処理")
}
executor.shutdown()
| 種類 | 用途 |
|---|---|
| newFixedThreadPool | 固定数 |
| newCachedThreadPool | 動的 |
| newSingleThreadExecutor | 直列処理 |
KotlinでThreadを直接使うべきか?
結論を明確にします。
現代Kotlinでは
- Threadは低レベルAPI
- 直接使う場面は限定的
主な使用ケース
- Javaライブラリとの統合
- Executor/ThreadFactory制御
- 非常に低レベルな並行処理
コルーチンとの関係
| 観点 | Thread | Coroutine |
|---|---|---|
| 実体 | OS/JVMスレッド | Kotlin管理 |
| コスト | 重い | 軽量 |
| 中断 | interrupt | suspend |
| 主用途 | 基盤 | アプリ設計 |
重要な整理
- コルーチンはThreadの上で動く
- Threadは「実行基盤」
- コルーチンは「設計レイヤー」
注意点
GlobalScope.launch は 学習用の最小例としてのみ登場することがあるが、実務では非推奨。
実際の開発では
- Android:
lifecycleScope,viewModelScope - サーバ:アプリ専用
CoroutineScope
を使うのが基本です。
ThreadとCoroutineの「数」の話について
よくある説明として、
- Threadは数百が限界
- Coroutineは数万可能
と語られることがありますが、これは固定値ではありません。
正確には
- Threadは
メモリ・OS制約・スタックサイズに強く依存 - Coroutineは
Threadより軽量だが無制限ではない
数値ではなく「性質の違い」として理解するのが正解です。
まとめ
- KotlinのThreadは Java Threadそのもの
- 仕組みは強力だが扱いは難しい
- 排他制御・ライフサイクル管理はバグ源になりやすい
- 実務ではコルーチンが第一選択
- Thread理解はコルーチンを正しく使うための基礎知識
以上、Kotlinのthreadについてでした。
最後までお読みいただき、ありがとうございました。










