Concurrent execution in Kotlin is done through threads and coroutines. A brief summary of threads is below; coroutines are handled in much more detail, as they are far more important in Kotlin programming.
Threads
Threads, as a concept, are well-used throughout the industry, from pthreads under Unix, to Java Thread objects. Depending on the implementation, a thread may have states like Running, Ready, Suspended, and Stopped. Typically only one thread (or one thread per core) is Running; but from the programmer's point of view, many threads are running simultaneously.
To launch a thread in Kotlin, use the thread() function.
val myThread = thread(start = true) {
// code
}
A coroutine is an instance of a suspendible computation. Think of a coroutine as a very lightweight thread. Similar to a thread, but not bound to a thread: a coroutine may suspend its execution in one thread and resume in a different thread.
Coroutine context and dispatchers are covered in the Kotlin documentation; a brief summary follows here.
Coroutine Scope
Coroutines are created within a coroutine scope. The simplest, and first, scope is called runBlocking { }, which will often be run from main() or a test function. You can create a coroutine scope with coroutineScope { }; the main difference is that runBlocking will create a scope and run it, but coroutineScope will create a scope and suspend it.
To create and start a coroutine, you can put it inside a launch block.
val job = launch {
println("running inside a coroutine")
}
A launch creates a Job object.
Similar to launch, async creates a coroutine, but instead of a Job, async returns a Deferred<T>, where T is the type of object returned. This is analogous to a Future<T> in Java.
val deferred = async {
6 * 7
}
println("The answer is ${deferred.await()}" // 42
Here deferred has type Deferred<Int>; the value of the block is the value of its final expression.
To communicate between coroutines, create channels. A Channel<T> will transport objects of type T between coroutines; the sender uses Channel.send(T), and the receiver will use Channel.receive() to receive a T. Either send(T) or receive() can block the calling coroutine: the sender, if the channel is "full", and the receiver if the channel is empty. It's possible to create a channel for which send(T) will never suspend, either because its capacity is limited only by memory, or because items are purged from the channel when a new item arrives.
There are some "standard" coroutine dispatchers:
When multiple threads or coroutines share the same objects, they can inadvertently stomp on each others' work. Kotlin has several data structures to avoid this trap. (As does Java.) These are provided in the package kotlin.concurrent.
Atomic Values
Kotlin provides AtomicInt, AtomicLong, and atomic array objects. These values are always updated atomically; that is, they are guaranteed not to change in unexpected ways when they are actively being updated.
java.util.Timer
The Java Timer class is a facility for threads to schedule execution of tasks. There are methods in kotlin.concurrent that use Timer objects.
java.util.concurrent
Don't forget about all of the Java concurrent collections classes. Java classes are interoperable with Kotlin classes.
As described in Best Practices In Java, when running concurrently in coroutines (or threads), the objects and methods that do the work should not carry state about the work being done, except in very simple circumstances. Rather, keep the context in a separate object. This way, worker methods can work multiple parallel processes, each with one context object.
[Prerequisite: Please read corresponding section in my Java BP article]
Encapsulation is one of the key concepts of Object Oriented Programming: an object has a single responsibility (the S in SOLID) that hides details of its implementation. In Best Practices In Java, we see several ways to further encapsulate within classes. All of these are also present in Kotlin. But there is an important addition to Lambda expressions in Kotlin: a lambda that has a single parameter may omit that parameter, and use the implicitly defined "it" in its place.
fun getStrings(): List<String> { /* code */ }
// ...
val myList = getStrings()
myList.forEach { println(it) }
Sometimes type names get really long–especially for generic types:
val updateTimeMap: Map<String, List<Pair<File, Instant>>> = ...
Instead, Kotlin provides typealias to shorten such things.
typealias UpdateTimeMap = Map<String, List<Pair<File, Instant>>>
val updateTimeMap: UpdateTimeMap = ...
This is exactly analogous to C's typedef. It does not create a new type, but merely provides "syntactic sugar" to shorten the name.