STM and Structured Concurrency
The Battle with Multithreading Dragons
Bartek „Koziołek” Kuczyński
Człowiek od robienia hmhmm.
https://koziolekweb.pl/001-o-mnie.html


Dorrik Hexhammer

Dwarf warrior-engineer
Lawful | Neutral | Chaos | |
---|---|---|---|
|
|||
|
|||
|
Alignment
Immutability
Atomics
Eventual Consistency
Lock
synchronized
Thread.sleep
Thread.yeld
Single Thread
Possible consistency
Good
Neutral
Evil
Motivation

Our Data protection

- We protect our code
- We know why we do that
- We can trace changes of code AND data
- White Box
Clients and Services

- We need to protect external code (lib or service)
- We read a doc ;)
- We don't know why we need to do that
- Black Box (probably mimic)
Processes

- Long running, multistep processes
- Some elements could be run in parallel
- We need to coordinate a whole process
- Grey Box
Single Object Synchronization
class Account {
private int balance = 0;
public synchronized void withdraw() {balance -= 1;}
public synchronized void deposit() {balance += 1;}
//…
}
class Account {
private final Object monitor = new Object();
private int balance = 0;
public void withdraw() {
synchronized (monitor) {
balance -= 1;
}
}
public void deposit() {
synchronized (monitor) {
balance += 1;
}
}
}
Quick reminder
synchronized(object){} -> object is monitor
synchronized method() -> synchronized(this){}
synchronized static method() -> synchronized(this.getClass()){}
Many Objects Synchronization
interface BankService {
void transfer(Account from, Account to, int value);
}
//…
public class SimpleAccount implements Account {
private int balance;
@Override
public void deposit(int depo) {
//…
balance += depo;
}
@Override
public void withdraw(int with) {
//…
balance -= with;
}
}
public void theProblem() {
var bob = new Account("Bob", RUNS);
var alice = new Account("Alice", RUNS);
var aliceToBob = prepare(RUNS,
() -> bankService.transfer(alice, bob, 1));
var bobToAlice = prepare(RUNS,
() -> bankService.transfer(bob, alice, 1));
var aliceThread = new Thread(aliceToBob, "Alice");
var bobThread = new Thread(bobToAlice, "Bob");
aliceThread.start();
bobThread.start();
aliceThread.join();
bobThread.join();
System.out.printf(STR."""
alice = \{alice}
bob = \{bob}""");
}
BankService implementation
method level synchronized
class MethodSync implements BankService {
public synchronized void transfer(Account from, Account to, int value) {
from.withdraw(value);
to.deposit(value);
}
}
BankService implementation
args level synchronized
class ArgumentSync implements BankService {
public void transfer(Account from, Account to, int value) {
synchronized (from) {
synchronized (to) {
from.withdraw(value);
to.deposit(value);
}
}
}
}
BankService implementation
ordered args level synchronized
class OrderedArgumentSync implements BankService {
public void transfer(Account from, Account to, int value, Comparator<Account> comparator) {
final int compare = comparator.compare(from, to);
if (compare > 0) {
synchronized (from) {
synchronized (to) {
from.withdraw(value);
to.deposit(value);
}
}
} else {
synchronized (to) {
synchronized (from) {
from.withdraw(value);
to.deposit(value);
}
}
}
}
}
BankService implementation
generic ordered args level synchronized
class GenericOrderedSync {
public <S1, S2, R> R perform(S1 s1, S2 s2, BiFunction<S1, S2, R> todo,
BiFunction<S1, S2, Integer> orderJudge) {
final Integer order = orderJudge.apply(s1, s2);
if (order > 0) {
synchronized (s1) {
synchronized (s2) {
return todo.apply(s1, s2);
}
}
} else {
synchronized (s2) {
synchronized (s1) {
return todo.apply(s1, s2);
}
}
}
}
}
Let's try another path in this dungeon

Dorrik new skill
The Aspect
The Aspect-Oriented Programming
Let's define annotation
@Target(METHOD)
@Retention(RUNTIME)
@Documented
public @interface ExtendedLock {
}
And pointcut
@Aspect
public class ExtendedLockAspect {
private final ExtendedLockHandler lockHandler = new ExtendedLockHandler();
@Around("@annotation(pl.koziolekweb.extendedlock.ExtendedLock)")
public Object callWithLock(ProceedingJoinPoint tjp) throws Throwable {
var args = tjp.getArgs();
try {
while (!lockHandler.lock(args)) { }
final Object proceed = tjp.proceed();
while (!lockHandler.unlock(args)) {}
return proceed;
} catch (Throwable t) {
while (!lockHandler.unlock(args)) {} throw t;
} finally {
while (!lockHandler.unlock(args)) {}
}
}
}
And handler
public boolean lock(Object[] elements) {
if (lock.tryLock()) {
clean();
for (Object element : elements) {
if (lockMap.containsKey(element)
&& (!lockMap.get(element).isSameThread(getCurrentThread()))) {
free();
return false;
}
}
for (Object element : elements) {
if (lockMap.containsKey(element))
lockMap.get(element).inc();
else
lockMap.put(element, new LockCounter(getCurrentThread()));
}
free();
return true;
}
return false;
}
Is it work?
To kill a Dragon, you need?
Sword
To make a sword, you need?
Iron
To make an iron, you need?
Cave
To make a cave, you need?
Mountain
To make a mountain, you need?
Kingdom
To make a kingdom, you need?
King
To make a king, you need?
Kill a dragon!
If only we have a spell…
The Database
Atomic

Consistency
Isolation
Durability

Dorrik new skill
The STM
Software Transactional Memory
STM Account
public class StmAccount implements Account {
private Ref balance;
@SneakyThrows
public void deposit(final int depo) {
LockingTransaction.runInTransaction(() -> {
balance.set((Integer) (balance.deref()) + depo);
return true;
});
}
@SneakyThrows
public void withdraw(int with) {
LockingTransaction.runInTransaction(() -> {
balance.set((Integer) (balance.deref()) - with);
return true;
});
}
}
BankService implementation
not synchronized
class MethodSync implements BankService {
public void transfer(Account from, Account to, int value) {
from.withdraw(value);
to.deposit(value);
}
}
STM Account
public class ExternalStmAccount implements Account {
private Ref balance;
@SneakyThrows
public void deposit(final int depo) {
balance.set((Integer) (balance.deref()) + depo);
}
@SneakyThrows
public void withdraw(int with) {
balance.set((Integer) (balance.deref()) - with);
}
}
BankService implementation
STM support
class StmSync implements BankService {
@SneakyThrows
public void transfer(Account from, Account to, int value) {
LockingTransaction.runInTransaction(() -> {
from.withdraw(value);
to.deposit(value);
return true;
});
}
}
Time to new adventure!
And more xp
Process Dragon

Process Dragon
-
Has head
- Semaphore
-
Has tail
- CountDownLatch
- CyclicBarrier
We have Phasers!

We have Phasers!
public class PhaserExample {
public static void main(String[] args) {
Phaser phaser = new Phaser(1);
new Thread(new Action("T1", phaser), "T1").start();
new Thread(new Action("T2", phaser), "T2").start();
phaser.arriveAndAwaitAdvance();
new Thread(new Action("T3", phaser), "T3").start();
new Thread(new Action("T4", phaser), "T4").start();
phaser.arriveAndDeregister();
}
}
We have Phasers!
class Action implements Runnable {
private final String threadName;
private final Phaser phaser;
public void run() {
phaser.arriveAndAwaitAdvance();
try {
System.out.println("start " + threadName);
Thread.sleep(1000);
if (Math.random() > 0.2) {
throw new IllegalStateException("Die!");
}
System.out.println("end " + threadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
phaser.arriveAndDeregister();
}
}
We have Phasers!
start T2
start T1
end T1
end T2
start T4
start T3
Exception in thread "T4" Exception in thread "T3"
java.lang.IllegalStateException: Die!
at pl.koziolekweb.simple.Action.run(PhaserExample.java:35)
at java.base/java.lang.Thread.run(Thread.java:1583)
java.lang.IllegalStateException: Die!
at pl.koziolekweb.simple.Action.run(PhaserExample.java:35)
at java.base/java.lang.Thread.run(Thread.java:1583)

Dorrik new skill
Structured
Concurrency
What to do?
class SimpleTask implements Callable<Void> {
public Void call() {
System.out.println("start " + threadName);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (Math.random() > 0.2) {
throw new IllegalStateException("Die!");
}
System.out.println("end " + threadName);
return null;
}
}
Back to fantasy word!
public class StructuredConcurrenceExample {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
StructuredTaskScope.Subtask<Void> t1 = scope.fork(new SimpleTask("T1"));
StructuredTaskScope.Subtask<Void> t2 = scope.fork(new SimpleTask("T2"));
scope.join().throwIfFailed();
StructuredTaskScope.Subtask<Void> t3 = scope.fork(new SimpleTask("T3"));
StructuredTaskScope.Subtask<Void> t4 = scope.fork(new SimpleTask("T4"));
scope.join().throwIfFailed();
}
}
}
You are in a bad dungeon!
We need custom scope!
Custom scope
class Sandbox<T> extends StructuredTaskScope<T> {
@Override
protected void handleComplete(Subtask<? extends T> subtask) {
if (subtask.state() == Subtask.State.FAILED) {
System.out.println(STR."""
end \{((SimpleTask) subtask.task()).getThreadName()}
but \{subtask.exception().getMessage()}""");
}
}
@Override
public Sandbox<T> join() throws InterruptedException {
super.join();
return this;
}
}
Custom scope
public class CustomScopeStructuredConcurrency {
public static void main(String[] args) throws InterruptedException {
try (var scope = new Sandbox<Void>()) {
StructuredTaskScope.Subtask<Void> t1 = scope.fork(new SimpleTask("T1"));
StructuredTaskScope.Subtask<Void> t2 = scope.fork(new SimpleTask("T2"));
scope.join();
StructuredTaskScope.Subtask<Void> t3 = scope.fork(new SimpleTask("T3"));
StructuredTaskScope.Subtask<Void> t4 = scope.fork(new SimpleTask("T4"));
scope.join();
}
}
}
Custom scope
start T2
start T1
end T2
end T1
but Die!
start T3
start T4
end T3
end T4
STM and Structured Concurrency – The Battle of Multithreading Dragons
By Bartek Kuczyński
STM and Structured Concurrency – The Battle of Multithreading Dragons
- 81