Exploring the Kotlin Standard Library - Part 3

Exploring the Kotlin Standard Library - Part 3

Jamie McCrindle

In Part 1 and Part 2 of this series, I went through the default Kotlin namespace and kotlin.io. In Part 3 I'll be going over kotlin.concurrent.

The public functions in kotlin.concurrent are all utilities for creating timers, threads or timer tasks e.g.

// create a fixed rate timer that prints hello world every 100ms
// after a 100ms delay
val fixedRateTimer = fixedRateTimer(name = "hello-timer",
        initialDelay = 100, period = 100) {
    println("hello world!")
}
try {
    Thread.sleep(1000)
} finally {
    fixedRateTimer.cancel();
}

Note: by default the timers are not daemon timers. Without the cancel the timer would run indefinitely. This can often cause issues if you have a timer deployed in a container (like Tomcat). Naming them is always helpful in finding rogue non-daemon timers.

The thread() method creates a new thread

// run in a different thread
thread() {
    println("async")
}
println("sync")

although I would usually use an ExecutorService rather than managing threads directly. Fortunately the Kotlin standard library provides a number of extension methods on Executor and ExecutorService too.

// create a single thread executor
val singleThreadPool = Executors.newSingleThreadExecutor();
try {
    // Executor and ExecutorService are extended with an
    // invoke method
    val future = singleThreadPool<String>{
        "async"
    }
    println("sync")
    println(future.get(10, TimeUnit.SECONDS))
} finally {
    singleThreadPool.shutdown();

java.util.Timer also gets a couple of extension methods: schedule and scheduleAtFixedRate

// create a daemon thread
val timer = Timer("schedule", true);

// schedule a single event
timer.schedule(1000) {
    println("hello world!")
}
// schedule at a fixed rate
timer.scheduleAtFixedRate(1000, 1000) {
    println("hello world!")
}

Once again, I'd normally use a ScheduledExecutorService in preference. The standard library doesn't provide extension methods on ScheduledExecutorService but it is easy enough to add our own:

/**
 * Extension method on implementations of ScheduledExecutorService to schedule
 * an action
 */
fun <V, T: ScheduledExecutorService> T.schedule(
        delay: Long,
        unit: TimeUnit = TimeUnit.MILLISECONDS,
        action: () -> V): ScheduledFuture<V> {
    return this.schedule(
            callable { action() },
            delay, unit);
}

Which can be used as follows:

val scheduledExecutor = Executors.newScheduledThreadPool(1)
try {
    scheduledExecutor.schedule(1000) {
        println("hello world")
    }
} finally {
    scheduledExecutor.shutdown()
}

The kotlin.concurrent package also provides additional methods for using locks e.g.:

val lock = ReentrantLock();
val result = lock.withLock {
    // access a locked resource
}
val readWriteLock = ReentrantReadWriteLock()
readWriteLock.read {
    // execute an action with a read lock
}
readWriteLock.write {
    // execute an action with a write lock
}

If instead of locking, our code needs to try the lock and do one thing if the resource is available, and another if it is locked, we could extend Lock as follows:

/**
 * Only run if you can acquire a lock
 */
public inline fun <T> Lock.tryLock(action: ()->T, alternative: ()->T): T {
    if(tryLock()) {
        try {
            return action()
        }
        finally {
            unlock();
        }
    } else {
        return alternative();
    }
}

Which could be used like this:

val tryLock = ReentrantLock();
val tryLockResult = tryLock.tryLock({
    // run if we can get a lock
}, {
    // run if we couldn't get a lock
});