0% found this document useful (0 votes)
46 views

Parallel & Concurrent Programming in kotlin

Uploaded by

shivnarayan I
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
46 views

Parallel & Concurrent Programming in kotlin

Uploaded by

shivnarayan I
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 53

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

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy