Parallel & Concurrent Programming in kotlin
Parallel & Concurrent Programming in kotlin
Parallel &
Concurrent
Programming
According to Wikipedia:
Motivation
● Faster runtime
● Improved responsiveness
Parallelism vs concurrency
Concurrency: processes vs threads
Single-threaded process Multi-threaded process
Preemptive vs cooperative scheduling
2 Java packages
Kotlin package
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.
fun main() {
val myThread = MyThread()
myThread.start()
}
run vs start
fun main() {
val myThread1 = MyThread()
myThread1.start() // OK
val myThread2 = MyThread()
myThread2.run() // Current thread gets blocked
}
Ways to create 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")
}
}
● 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
sched
notify wait
timeout sleep
… …
Waiting
Blocked
Ways to manipulate a thread's state
● myThread.join() — Causes the current thread to wait for another thread to finish
● 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 LockedCounter {
private var c = 0
fun increment() {
lock.withLock { c++ }
}
}
The lock interface
● lock.withLock { } — Executes a lambda with the lock held (has try/catch inside)
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--
}
}
● 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
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
class PositiveLockedCounter {
private var c = 0
private val rwLock = ReadWriteReentrantLock()
fun increment() {
rwLock.write { c++ }
}
fun decrement() {
rwLock.write { c-- }
}
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
Well-synchronized = Data-race-free
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
A program is data-race-free if, for every possible execution of this program, no two events
form a data race.
JMM: Atomics
class Counter {
private val c = AtomicInteger()
fun increment() {
c.incrementAndGet()
}
fun decrement() {
c.decrementAndGet()
}
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