Kotlin
Parallel &
Concurrent
Programming
@kotlin | Developed by JetBrains
Definition
According to Wikipedia:
● Parallel computing is a type of computing “in which many calculations or processes
are carried out simultaneously”.
● Concurrent computing is a form of computing in which several computations are
executed concurrently – in overlapping time periods – instead of sequentially.
● It is possible to have parallelism without concurrency, and concurrency without
parallelism.
Motivation
● Faster runtime
● Improved responsiveness
Parallelism vs concurrency
Concurrency: processes vs threads
Single-threaded process Multi-threaded process
Preemptive vs cooperative scheduling
Preemptive: OS interrupts Cooperative: task yields
tasks control
Parallel and concurrent Programming
in the JVM
● The JVM has its own scheduler
○ It is independent from the OS scheduler
○ A JVM thread != an OS thread
○ => Multithreaded JVM apps can run on a single-threaded OS
● (DOS) JVM threads are either daemons or user threads.
● The app stops when all user threads are done.
● The JVM does not wait for daemon threads to finish.
Parallel programming in the JVM
2 Java packages
● java.lang contains basic primitives: Runnable, Thread, etc
● java.util.concurrent contains synchronization primitives and concurrent data structures
Kotlin package
● kotlin.concurrent — Wrappers and extensions for Java classes
Throwback: Single abstract method
interfaces
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Interface with a single method. We can instantiate it with a lambda.
class RunnableWrapper(val runnable: Runnable)
val myWrapperObject =
RunnableWrapper(
object : Runnable {
override fun run() {
println("I run")
}
}
)
val myWrapperLambda = RunnableWrapper { println("yo") }
Ways to create threads
You can inherit from the Thread class, which also implements Runnable.
class MyThread : Thread() {
override fun run() {
println("${currentThread()} is running")
}
}
fun main() {
val myThread = MyThread()
myThread.start()
}
run vs start
Never call Thread.run()!
run will execute on your thread, while start will create a new thread where run will be
executed.
fun main() {
val myThread1 = MyThread()
myThread1.start() // OK
val myThread2 = MyThread()
myThread2.run() // Current thread gets blocked
}
Ways to create threads
You can implement the Runnable interface and pass it
to a thread. You can pass the same Runnable to
several threads.
fun main() {
val myRunnable = Runnable { println("Sorry, gotta run!") }
val thread1 = Thread(myRunnable)
thread1.start()
val thread2 = Thread(myRunnable)
thread2.start()
}
Ways to create threads
Kotlin has an even simpler way to create threads, but under the hood the same old thread is
created and started.
import kotlin.concurrent.thread
fun main() {
val kotlinThread = thread {
println("I start instantly, but you can pass an option to start me later")
}
}
This is the preferable way to create threads.
Thread properties
A thread's properties cannot be changed after it is started.
Main properties of a thread:
● id: Long — This is the thread's identifier
● name: String
● priority: Int — This can range from 1 to 10, with a larger value indicating higher priority
● daemon: Boolean
● state: Thread.state
● isAlive: Boolean
State of a thread
state isAlive
NEW false
RUNNABLE true
BLOCKED true
WAITING true
TIMED_WAITING true
TERMINATED false
State of a thread
yield
New Runnable Running Terminated
start terminate
sched
notify wait
timeout sleep
… …
Waiting
Blocked
Ways to manipulate a thread's state
● val myThread = thread { ... } — Creates a new thread
● myThread.start() — Starts a thread
● myThread.join() — Causes the current thread to wait for another thread to finish
● sleep(...) — Puts the current thread to sleep
● yield() — Tries to step back `
● myThread.interrupt() — Tries to interrupt a thread
● myThread.isInterrupted() — Checks whether thread was interrupted
● interrupted() — Checks and clears the interruption flag
sleep, join, yield, interrupt
● The sleep and yield methods are only applicable to the current thread, which means
that you cannot suspend another thread.
● All blocking and waiting methods can throw InterruptedException
Classic worker
class ClassicWorker : Runnable {
override fun run() {
try {
while (!Thread.interrupted()) {
// do stuff
}
} catch (e: InterruptedException) {} // absolutely legal empty catch block
}
}
Parallelism and shared memory:
Examples of problematic interleaving
Parallel threads have access to the same shared memory.
This often leads to problems that cannot arise in a single-threaded environment.
class Counter { Both operations on c are single, simple statements.
private var c = 0
However, even simple statements can be translated
fun increment() { into multiple steps by the virtual machine, and those
c++ steps can be interleaved.
}
fun decrement() {
c--
}
fun value(): Int {
return c
}
}
Parallelism and shared memory:
Examples of problematic interleaving
Parallel threads have access to the same shared memory.
This often leads to problems that cannot arise in a single-threaded environment.
class Counter { Suppose both Thread#1 and Thread#2 invoke
private var c = 0 increment at the same time. If the initial value of c is
0, their interleaved actions might follow this
fun increment() { sequence:
c++
● T#1: Read value 0 from c.
}
fun decrement() { ● T#2: Read value 0 from c.
c--
} ● T#1: Increment value — result is 1.
fun value(): Int {
● T#1: Write result 1 to c.
return c
} ● T#2: Increment value — result is 1.
}
● T#2: Write result 1 to c.
Synchronization mechanisms
● Mutual exclusion, such as Lock and the synchronized keyword
● Concurrent data structures and synchronization primitives
● Atomics, which work directly with shared memory (DANGER ZONE)
Locks
class LockedCounter {
private var c = 0
private val lock = ReentrantLock()
fun increment() {
lock.withLock { c++ }
}
// same for other methods
…
}
The lock interface
● lock.lock() — Acquires the lock
● lock.tryLock() — Tries to acquire the lock
● lock.unlock() — Releases the lock
● lock.withLock { } — Executes a lambda with the lock held (has try/catch inside)
● lock.newCondition() — Creates a condition variable associated with the lock
Conditions
class PositiveLockedCounter {
private var c = 0
A condition allows a thread holding a lock
private val lock = ReentrantLock() to wait until another thread signals it
private val condition = lock.newCondition()
about a certain event. Internally, the
fun increment() {
lock.withLock {
await method releases the associated lock
c++ upon call, and acquires it back before
condition.signal()
} finally returning it again.
}
fun decrement() {
lock.withLock {
while (c == 0) {
condition.await()
}
c--
}
}
fun value(): Int {
return lock.withLock { c }
}
}
The ReentrantLock class
● ReentrantLock – Allows the lock to be acquired multiple times by the same thread
● lock.getHoldCount() – Gets the number of holds on this lock by the current thread
● lock.queuedThreads() – Gets a collection of the threads waiting on this lock
● lock.isFair() – Checks the fairness of the lock
The synchronized statement
In the JVM, every object has an intrinsic lock associated with it (aka a monitor).
class Counter {
private var c = 0
fun increment() {
synchronized(this) { c++ }
}
…
}
Synchronized method
Java Kotlin
public class SynchronizedCounter { class SynchronizedCounter {
private int c = 0; private var c = 0
public synchronized void increment() { @Synchronized
c++; fun increment() {
} c++
}
… …
} }
The ReadWriteLock class
ReadWriteLock allows multiple readers to access a resource concurrently but only lets a
single writer modify it.
● rwLock.readLock() – Returns the read lock
● rwLock.writeLock() – Returns the write lock
● rwLock.read { ... } – Executes lambda under a read lock
● rwLock.write { ... } – Executes lambda under a write lock
The ReadWriteLock Class
class PositiveLockedCounter {
private var c = 0
private val rwLock = ReadWriteReentrantLock()
fun increment() {
rwLock.write { c++ }
}
fun decrement() {
rwLock.write { c-- }
}
fun value(): Int {
return rwLock.read { c }
}
}
Concurrent blocking collections
java.util.concurrent is a Java package that implements both blocking and non-blocking
concurrent collections, such as:
● SynchronousQueue – One-element rendezvous channel
● ArrayBlockingQueue – Fixed-capacity queue
● LinkedBlockingQueue – Unbounded blocking queue
● PriorityBlockingQueue – Unbounded blocking priority queue
Concurrent non-blocking collections
java.util.concurrent is a Java package that implements both blocking and
non-blocking concurrent collections, such as:
● ConcurrentLinkedQueue – Non-blocking unbounded queue
● ConcurrentLinkedDequeue – Non-blocking unbounded dequeue
● ConcurrentHashMap – Concurrent unordered hash-map
● ConcurrentSkipListMap – Concurrent sorted hash-map
Synchronization primitives
java.util.concurrent also implements concurrent data structures and synchronization
primitives.
● Exchanger – Blocking exchange
● Phaser – Barrier synchronization
Java Memory Model: Weak behaviors
There are no guarantees when it comes to ordering! Possible outputs:
class OrderingTest { ● 0, 0
var x = 0 ● 0, 1
var y = 0
● 1, 1
fun test() {
thread { ● 1, 0
x=1
y=1
}
thread {
val a = y
val b = x
println("$a, $b")
}
}
}
Java Memory Model: Weak behaviors
There are no guarantees when it comes to progress! Possible outputs:
class ProgressTest { ● "I am free!"
var flag = false ● …
fun test() { ● …
thread { ● …
while (!flag) {} ● hang!
println("I am free!")
}
thread { flag = true }
}
}
Java Memory Model: Weak behaviors
There are no guarantees when it comes to progress! Possible outputs:
class ProgressTest { ● "I am free!"
var flag = false ● …
fun test() { ● …
thread { ● …
while (true) {} ● hang!
println("I am free!")
}
thread { flag = true }
}
}
JMM: Data-Race-Freedom Guarantee
But what does JMM guarantee?
Well-synchronized programs have simple interleaving semantics.
JMM: Data-Race-Freedom Guarantee
But what does JMM guarantee?
Well-synchronized programs have simple interleaving semantics.
Well-synchronized = Data-race-free
Simple interleaving semantics = Sequentially consistent semantics
Data-race-free programs have sequentially consistent semantics
JMM: Volatile fields
Volatile fields can be used to restore sequential consistency.
class OrderingTest { class ProgressTest {
@Volatile var x = 0 @Volatile var flag = false
@Volatile var y = 0 fun test() {
fun test() { thread {
thread { while (!flag) {}
x=1 println("I am free!")
y=1 }
} thread { flag = true }
thread { }
val a = y }
val b = x
println("$a, $b")
}
}
}
JMM: Volatile fields
Volatile variables can be used for synchronization. How do we know there is enough
synchronization?
class OrderingTest {
var x = 0
@Volatile var y = 0
fun test() {
thread {
x=1
y=1
}
thread {
val a = y
val b = x
println("$a, $b")
}
}
}
JMM: Happens-before relation
Wx0
Wy0
class OrderingTest {
po po
var x = 0
@Volatile var y = 0 V
fun test() { Wx1 Ry1
rf
thread { po po
x=1 V
rf
y=1 Wy1 Rx1
}
thread {
po
val a = y program-order
val b = x rf
println("$a, $b") reads-from
}
}
}
JMM: Happens-before relation
Wx0
Wy0
class OrderingTest {
po po
var x = 0
@Volatile var y = 0 V
fun test() { Wx1 Ry1
rf
thread { po po
x=1 V
sw
y=1 Wy1 Rx1
}
thread {
po
val a = y program-order
val b = x rf
println("$a, $b") reads-from
} sw
Synchronizes-with
}
-e.g. reads-from on Volatile field
}
JMM: Happens-before relation
Wx0
Wy0
class OrderingTest {
po po
var x = 0
@Volatile var y = 0 V
fun test() { Wx1 Ry1
hb
thread { po po
x=1 V
sw
y=1 Wy1 Rx1
}
thread {
po
val a = y program-order
val b = x rf
println("$a, $b") reads-from
} sw
Synchronizes-with
}
-e.g. reads-from on Volatile field
}
hb
happens-before
= (po ∪ sw)+
JMM: Happens-before relation
Wx0
Wy0
class OrderingTest {
po po
var x = 0
rf
@Volatile var y = 0 V
fun test() { Wx1 Ry1
hb
thread { po po
x=1 V
sw
y=1 Wy1 Rx0
}
thread {
po
val a = y program-order
val b = x rf
println("$a, $b") reads-from
} sw
Synchronizes-with
}
-e.g. reads-from on Volatile field
}
hb
happens-before
= (po ∪ sw)+
JMM: Synchronizing actions
● Read and write for volatile fields
● Lock and unlock
● Thread run and start, as well as finish and join
JMM: DRF-SC again
Two events form a data race if:
● Both are memory accesses to the same field.
● Both are plain (non-atomic) accesses.
● At least one of them is a write event.
● They are not related by happens before.
Data-race-free programs have sequentially consistent semantics
A program is data-race-free if, for every possible execution of this program, no two events
form a data race.
JMM: Atomics
But what about atomic operators on shared variables?
class Counter {
private val c = AtomicInteger()
fun increment() {
c.incrementAndGet()
}
fun decrement() {
c.decrementAndGet()
}
fun value(): Int {
return c.get()
}
}
JMM: Atomics
Atomic classes from package the java.util.concurrent.atomic package:
● AtomicInteger
● AtomicLong
● AtomicBoolean
● AtomicReference
And their array counterparts:
● AtomicIntegerArray
● AtomicLongArray
● AtomicReferenceArray
JMM: Atomics
● get() – Reads a value with volatile semantics
● set(v) – Writes a value with volatile semantics
● getAndSet(v) – Atomically exchanges a value
● compareAndSet(e, v) – Atomically compares a value of atomic variable with the
expected value, e, and if they are equal, replaces content of atomic variable with the
desired value, v; returns a boolean indicating success or failure.
● compareAndExchange(e, v) – Atomically compares a value with an expected value, e,
and if they are equal, replaces with the desired value, v; returns a read value.
● getAndIncrement(), addAndGet(d), etc – Perform Atomic arithmetic operations for ●
numeric atomics (AtomicInteger, AtomicLong).
● …
JMM: Atomics
Methods of atomic classes:
● …
● getXXX()
● setXXX(v)
● weakCompareAndSetXXX(e, v)
● compareAndExchangeXXX(e, v)
In these cases, XXX is an access mode: Acquire, Release, Opaque, Plain
You can learn more about Java Access Modes here:
https://gee.cs.oswego.edu/dl/html/j9mm.html
JMM: Atomics Problem
class Node<T>(val value: T) {
val next = AtomicReference<Node<T>>()
}
JMM: Atomic field updaters
Use AtomicXXXFieldUpdater classes to directly modify volatile fields:
class Counter {
@Volatile private var c = 0
companion object {
private val updater = AtomicIntegerFieldUpdater.newUpdater(Counter::class.java, "c")
}
fun increment() {
updater.incrementAndGet(this)
}
fun decrement() {
updater.decrementAndGet(this)
}
fun value(): Int {
return updater.get(this)
}
}
Starting from JDK9, there is also the VarHandle class, which serves a similar purpose.
Kotlin: AtomicFU
The AtomicFU library is a recommended way to use atomic operations in Kotlin:
https://github.com/Kotlin/kotlinx-atomicfu
● It provides AtomicXXX classes with API similar to
class Counter {
Java atomics.
private val c = atomic(0)
fun increment() { ● Under the hood compiler plugin replaces usage of
c += 1 atomics to AtomicXXXFieldUpdater or VarHandle.
}
fun decrement() { ● It also provides convenient extension functions, e.g.
c -= 1 c.update { it + 1 }
}
fun value(): Int {
return c.value
}
}
Thanks!
@kotlin | Developed by JetBrains