Kotlinのthreadについて

採用はこちら

目次

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スレッドは以下の状態を持ちます。

  1. NEW
    生成されたが未開始
  2. RUNNABLE
    JVM上で実行可能な状態
    ※「実行中」と「実行待ち」の両方を含む
  3. BLOCKED / WAITING / TIMED_WAITING
    ロック待ち、待機中、時間付き待機
  4. TERMINATED
    実行終了
thread {
    Thread.sleep(1000)
    println("1秒後に実行")
}
  • Thread.sleep()現在のスレッドを停止
  • UIスレッドで使うとフリーズの原因になるため注意

スレッドで最も危険な問題:共有データ

典型的なレースコンディション

var count = 0

thread {
    repeat(1000) { count++ }
}
thread {
    repeat(1000) { count++ }
}

一見すると count は 2000 になりそうですが、そうならないことがあります

理由

  • count++
    1. 読み取り
    2. 加算
    3. 書き込み
      の複合操作であり、原子的ではない

排他制御(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制御
  • 非常に低レベルな並行処理

コルーチンとの関係

観点ThreadCoroutine
実体OS/JVMスレッドKotlin管理
コスト重い軽量
中断interruptsuspend
主用途基盤アプリ設計

重要な整理

  • コルーチンはThreadの上で動く
  • Threadは「実行基盤」
  • コルーチンは「設計レイヤー」

注意点

GlobalScope.launch学習用の最小例としてのみ登場することがあるが、実務では非推奨

実際の開発では

  • Android:lifecycleScope, viewModelScope
  • サーバ:アプリ専用 CoroutineScope

を使うのが基本です。

ThreadとCoroutineの「数」の話について

よくある説明として、

  • Threadは数百が限界
  • Coroutineは数万可能

と語られることがありますが、これは固定値ではありません

正確には

  • Threadは
    メモリ・OS制約・スタックサイズに強く依存
  • Coroutineは
    Threadより軽量だが無制限ではない

数値ではなく「性質の違い」として理解するのが正解です。

まとめ

  • KotlinのThreadは Java Threadそのもの
  • 仕組みは強力だが扱いは難しい
  • 排他制御・ライフサイクル管理はバグ源になりやすい
  • 実務ではコルーチンが第一選択
  • Thread理解はコルーチンを正しく使うための基礎知識

以上、Kotlinのthreadについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次