Kotlin Libraries: Concurrency

Today’s Kotlin library article is about the kotlin.concurrent package, and everything that adds to the platform.

Java’s concurrency package is already quite sophisticated, and rather than re-invent so many extremely delicate abstractions, the Kotlin authors focused on making the libraries better suited to the language by decorating and shortening various features.

Thread Local additions

Thread local gets a new “get-or-set” defaulting function. This allows for an arbitrarily complex block of code to be used to default the thread local.

This is complementary to the ``

1fun main(args: Array<String>) {
2    val tlocal = ThreadLocal<String>()
3    println(tlocal.getOrSet(::loadLocal)) // prints "initial-value"
4    tlocal.set("updated-value")
5    println(tlocal.getOrSet(::loadLocal)) // prints "updated-value"
6}
7
8// Could be anything
9fun loadLocal() = "initial-value"

Locks

Next on the hit list is lock management. In Java it’s common to write this:

1ReentrantLock lock = new ReentrantLock();
2lock.lock();
3try {
4  threadUnsafe();
5} finally {
6  lock.unlock();
7}

This of course opens the opportunity to forget to call unlock or to write this boilerplate pattern in the wrong way. For example, you can write it this way which is not handled well by some counting lock implementations if the lock acquisition fails:

1try {
2  lock.lock(); // might fail!
3  threadUnsafe();
4} finally {
5  lock.unlock();
6}

Kotlin adds a withLock function for this purpose:

1fun main(args: Array<String>) {
2    val lock = ReentrantLock()
3    lock.withLock {
4        threadUnsafe()
5    }
6}

Two immediate benefits:

  1. This is added to Lock interface, which means all lock implementations can take advantage of it
  2. It is an inline function so you don’t pay for any additional overhead from the standard Java implementation.

They have done this similarly for ReentrantReadWriteLock readers and writers:

1  val lock = ReentrantReadWriteLock()
2
3  lock.read {
4      readerFunction()
5  }
6
7  lock.write {
8      writerFunction()
9  }

Again, these are inlined so you don’t actually pay any additional dispatch cost for the cleanliness.

Quick and Dirty Threading

In Java if you just want to spin off a singular thread to do some work, it probably looks like this:

1Thread t = new Thread(() -> doSomeWork(), "worker-thread");
2t.start();

Kotlin has a convenience function to create a thread that, by default, automactically starts the thread (and has named parameters for a variety of other fields, including the name):

1val t = thread(name="worker-thread") = doSomeWork()
2// already running here.

Timers

Similarly, they have factory functions for timers, as well. They predominantly exist to cope with the fact TimerTask is an abstract class, and as such cannot be traditionally converted into a lambda.

There are a series of global factory functions that auto schedule the timer:

1val t1 = timer(name = "timer-1", initialDelay = 0, period = 1000) { println("fixed delay after initial delay") }
2val t2 = timer(name = "timer-2", startAt = Date.from(Instant.now()), period = 1000) { println("fixed delay after initial start time") }
3val t3 = fixedRateTimer(name = "fixed-rate-1", initialDelay = 0, period = 1000) { println("fixed rate after initial delay") }
4val t4 = fixedRateTimer(name ="fixed-rate-2", startAt = Date.from(Instant.now()), period = 1000) { println("fixed rate after initial delay") }

Finally, there are also extension methods added to the timer to schedule with lambdas as well:

 1val t5 = Timer("timer-3")
 2t5.schedule(delay = 1000) { println("run once after delay") }
 3val t6 = Timer("timer-4")
 4t6.schedule(delay = 1000, period = 1000) { println("fixed delay after initial delay") }
 5val t7 = Timer("timer-5")
 6t7.schedule(time = Date.from(Instant.now()), period = 1000) { println("fixed delay after initial start time") }
 7val t8 = Timer("fixed-rate-3")
 8t8.scheduleAtFixedRate(delay = 0, period = 1000) { println("fixed rate after initial delay") }
 9val t9 = Timer("fixed-rate-4")
10t9.scheduleAtFixedRate(time = Date.from(Instant.now()), period = 1000) { println("fixed rate after initial start time") }
comments powered by Disqus