スレッドを生成する
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
でオブジェクトが作られることが問題だと思う。オブジェクトをキャッシュするとか何か上手い工夫が必要かな。