読者です 読者をやめる 読者になる 読者になる

算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

KotlinのConcurrencyライブラリを使う

Kotlin プログラミング メモ

スレッドを生成する

Javaの標準ライブラリを使えばこんな感じ。

fun main(args: Array<String>) {
    Thread(object: Runnable {
        override fun run() {
            println("Done.")
        }
    }).start()
}

実は、1つの抽象メソッドを持つインタフェースのインスタンスはより簡単に書ける*1

fun main(args: Array<String>) {
    Thread {
        println("Done.")
    }.start()
}

これでも十分簡単なんだけど、Kotlin標準ライブラリ(kotlin.concurrentパッケージ)にはThreadのためのファクトリ関数がある。引数はrun()相当の関数オブジェクトを必ず取り、いくつかのオプションがある。デフォルトではThreadインスタンス生成後、すぐにstart()されるので上記のコードは次のように書ける。

import kotlin.concurrent.thread

fun main(args: Array<String>) {
    thread {
        println("Done.")
    }
}

synchronizedなブロック

KotlinにはJavaのようなsynchronizedなブロックを形成する組み込み構文はないっぽい。Java 5で導入されたjava.util.concurrent.locks.Lockインタフェースやその実装クラスを使う。それに加えてKotlin標準ライブラリが提供する拡張関数なんかを使うとキレイで簡単にコードを記述できる。関数リテラルや構文糖衣によって関数呼び出しなどを組み込み構文っぽく書けるのが特徴のKotlinだからできる芸当。

次のシグネチャの関数がKotlin標準ライブラリで提供されている。

fun <T> Lock.withLock(action: () -> T): T 

action関数の実行の前後でlock(), unlock()がきちんと呼ばれる、Lockインタフェースの拡張関数だ。

import kotlin.concurrent.thread
import kotlin.concurrent.withLock

fun main(args: Array<String>) {
    val lock = java.util.concurrent.locks.ReentrantLock()

    (1..10).forEach {
        thread {
            lock.withLock {
                print("<")
                Thread.sleep(500)
                print(">")
            }
        }
    }
}

こんな感じに使う。期待通りに次のような出力が得られる。

<><><><><><><><><><>

withLockを使わない場合の出力はこう。

<<<<<<<<<<>>>>>>>>>>

スレッドを待ち合わせる

Effective Javaの項目69「waitとnotifyよりコンカレンシーユーティリティを選ぶ」をKotlinは大切にしている。だからKotlinオブジェクトはwait()notify()を持たない。上記同様、代わりとしてjava.util.concurrentやKotlin標準ライブラリを使ってなんとかする。例えばCountDownLatch

import kotlin.concurrent.thread
import kotlin.concurrent.latch

fun main(args: Array<String>) {
    2.latch {
        thread {
            Thread.sleep(500)
            println("A")
            countDown()
        }

        thread {
            Thread.sleep(200)
            println("B")
            countDown()
        }

        println("C")
    }

    // 2つのスレッドが終了するのを待つ
    println("D")
}

Int型の拡張関数としてファクトリ関数が定義されているのが面白い。この関数呼び出しはnew CountDownLatch(2)と同じようにインスタンスを生成し、引数のCountDownLatch拡張関数オブジェクトを実行して、カウントが終わるのを待つ。このコードの実行結果は次のとおり*2

C
B
A
D

Executorを使う

kotlin.concurrentパッケージにはExecutorExecutorServiceの拡張関数も用意されている。 下記の例ではinvoke関数によりexecutorオブジェクトを関数として呼び出しているような記述ができる。

import kotlin.concurrent.invoke

fun main(args: Array<String>) {
    val executor = java.util.concurrent.Executors.newFixedThreadPool(2)
    (1..6).forEach {
        executor {
            if(it % 2 != 0) Thread.sleep(100)
            println(it)
        }
    }
    executor.shutdown()
}

実行結果。

2
1
4
3
6
5

Timer, TimerTaskを使う

TimerTimerTaskの使い勝手も少し良くなっている。

import java.util.Timer
import kotlin.concurrent.schedule

fun main(args: Array<String>) {
    Timer().schedule(1000, 500) {
        println("HELLO")
    }
}

起動してから1000ミリ秒経過後にHELLOを500ミリ秒間隔で出力する。これと同じコードはもっと簡単に書ける。

import kotlin.concurrent.timer

fun main(args: Array<String>) {
    timer(initialDelay = 1000, period = 500) {
        println("HELLO")
    }
}

TimeUnit

Kotlin標準ライブラリにはTimeUnitを使いやすくするようなAPIは現時点で、ない。 ないんだけど例えば次のようなAPIを作ることは簡単にできる。

fun main(args: Array<String>) {
    3.seconds.sleep()
    println("3 sec. elapsed")
}

拡張プロパティ(拡張関数のプロパティ版)を使って、この読みやすいコードを実現してる。

import java.util.Timer
import java.util.concurrent.TimeUnit

class Time(val timeUnit: TimeUnit, val duration: Long) {
    fun sleep() = timeUnit.sleep(duration)
}

val Int.seconds: Time
get() = Time(TimeUnit.SECONDS, this.toLong())

この実装は3.secondsでオブジェクトが作られることが問題だと思う。オブジェクトをキャッシュするとか何か上手い工夫が必要かな。

*1:SAM Constructorという機能で実現している。http://blog.jetbrains.com/kotlin/2013/04/kotlin-m5-2-intellij-idea-12-1-and-gradle/ を参照

*2:実行結果は環境に依存する。