スレッドを生成する
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パッケージにはExecutorやExecutorServiceの拡張関数も用意されている。
下記の例では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を使う
TimerやTimerTaskの使い勝手も少し良くなっている。
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でオブジェクトが作られることが問題だと思う。オブジェクトをキャッシュするとか何か上手い工夫が必要かな。