Introduction to
Concurrency:
Synchronization Mechanisms
Mutual Exclusion
• The ability to allow single access to a critical section at any given time
• A process remains inside the critical section for a finite time only
• A process is not delayed if the no other process in critical section
• This ability is never interrupted (Thread.interrupt() will not affect it)
• Can be implemented using software or hardware:
• Locks - OS level (software)
• Mutex, Semaphore, ReadWriteLock
• Spin-locks
• Using atomic operations – Hardware level (CPU instructions)
2
Processes States
3
Monitors
• Programming language constructs that control access to shared data.
• Monitors contain:
• Lock – allows mutual exclusion
• Condition variables – allows proper scheduling
• Monitors Ensure:
• Shared data structure
• Protests the data from incorrect concurrent access or modification.
• Procedures
• Ensures procedures do not conflict when ran concurrently.
• Synchronization
• Enforces synchronization between concurrent procedure invocation.
• In other words: allows mutual exclusion
• Example: Java “synchronized” keyword.
4
Hoare: Monitor Styles
• The thread uses notify() and releases the lock (gets suspended)
• The notified (waiting) thread acquires the lock
• The lock is transferred from the notifying to the notified thread
• It assumes condition is met!
• Once done, it returns the lock to the thread initiated notify()
5
Mesa: Monitor Styles
• The thread uses notify() keeps the lock
• The notified threads – is blocked (ready queue) until they acquire it.
• It competes with othe notified threads competing for the lock
• Once acquired the thread checks if the condition is met
• If met, executes the critical section, and then releases the lock.
• If not, goes back into blocked mode releasing the lock.
6
Mutex
• Special objects are owned by a thread.
• Used to allow mutual exclusion for critical sections.
• By using wait()/notify().
• Wait() – causes the thread to enter wait mode
• Notify() – “awakens” a thread and moves it from wait to blocked mode
• Thread moves to running mode once it acquires the lock
• Thread must unlock which he has locked.
• In case of locking failure, the owning thread goes to wait mode.
7
C++14 Mutex Example
1 #include <iostream> #include <vector> #include <string>
2 #include <chrono> #include <thread> #include <mutex>
3
4 int ctr = 0;
5 std::mutex mutex;
6
7 void increment(int n) {
8 int i = 0;
9 while (i < 10) {
10
mutex.lock(); //entering critical section
11
ctr++;
12
mutex.unlock(); //leaving critical section
13
i++;
14 }
15 }
8
What is the output result?
16 int main() {
17
std::vector<std::thread> v;
18
for (int n = 0; n < 10; ++n) {
19
v.emplace_back(increment, n); //runs constructor of std::thread
20
}
21
for (auto& t : v) {
22
t.join();
23
}
24
std::cout << ctr << std::endl;
25 }
9
Semaphore
• An integer used to keep track of resources available among processes.
• Allows limited reading/writing access, depending on its integer value.
• What about binary Semaphore? Acts like a mutex!
• API contains three operations:
• Initialize
• decrement
• increment
• Uses wait():
• value = 1
• decrement() is called
• Uses notify():
• value = 0
• increment() is called
10
Pseudo Code: Semaphore
• Semaphore(max, fifo)
• Creates a new semaphore with the given maximum value and fifo flag.
• If fifo is true, threads block in a FIFO queue so a release always wakes the one blocked the
longest;
• otherwise they block in a set and a release wakes an arbitrary blocked thread.
• acquire()
atomically {if (value > 0) value--; else block on s}
• release()
atomically {
if (there are threads blocked on s)
wake one of them
else if (value == MAX) //optional
fail
else
value++
}
11
Java8 Binary Semaphore Example
1 class Counter{
2
int ctr =0;
3
Semaphore sem = new Semaphore(1);
4
5
public void inc() throws InterruptedException{
6
sem.acquire();
7
ctr++;
8
sem.release();
9
}
10
11
public String toString(){
12
return Integer.toString(ctr);
13
}
14 }
12
What is the output result?
1 public static void main(String[] args) throws InterruptedException {
2
Counter ctr = new Counter();
3
4
for (int i=0;i<10;i++){
5
Runnable r = ()-> { for (int j=0;j<10;j++) ctr.inc(); };
6
new Thread(r).start();
7
}
8
Thread.sleep(500); //main does not wait for threads
9
System.out.println(ctr.toString());
10 }
13
Atomic Operations: Hardware Mutual Exclusion
• Instruction implemented in the hardware level (CPU instruction)
• An atomic operation works by locking the affected memory address in
the CPU (shared L3) cache.
• The CPU acquires the memory address exclusively in its cache
• It does not permit any other CPU to acquire or share that address until the
operation completes.
• If value is in cache, other CPU will not access original memory address but
instead, will access the cache address.
• Can be interrupted!
14
Hardware Instruction Example
• Test-and-Set:
1 bool test_and_set (bool *lock){
2 bool oldval = *lock;
3 *word = true;
4 return oldval;
5 }
• The entire function runs atomically:
• Tests a lock – by returning its value
• Sets the lock
• If returned value is false, then the lock is obtained.
• If returned value is true, then the lock is already in use.
15
Spinlocks
• Uses ‘busy-waiting’ until lock is
acquired.
• Suitable for short period
• Threads do not go to wait
mode!
• As long as test_and_set returns
true, we are locked into a loop.
• Once a false value is returned, we
run the critical section.
• Once done, we change the lock
value back to false.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool flag = false;
int counter = 0;
void increment(){
int i = 0;
while(i < 10){
while(test_and_set(&flag)); //spinlock
counter++; flag = false; i++;
}
}
int main() {
std::vector<std::thread> v;
for (int n = 0; n < 10; ++n) {
v.emplace_back(count, n); //runs ctor of std::thread
}
for (auto& t : v) {
t.join();
}
std::cout << ctr << std::endl;
}
16
C++14 Spinlocks
• C++ has built in support for atomic instructions.
• We can use test_and_set to build a spinlock class.
• Class “atomic”:
• std::atomic_flag
• test_and_set(std::memory_order)
• Modes:
• std::memory_order_release
• std::memory_order_acquire
• See example next slide.
17
1 #include <thread>
2 #include <vector>
3 #include <iostream>
4 #include <atomic>
5
6 std::atomic_flag lock = ATOMIC_FLAG_INIT;
7 int ctr = 0;
8
9 void count(int n) {
10 for (int cnt = 0; cnt < 10; ++cnt) {
11
while (lock.test_and_set(std::memory_order_acquire)) // acquire lock
12
; // spin
13
ctr++;
14
lock.clear(std::memory_order_release); // release lock
15
}
16 }
17 }
18
What is the output result?
16 int main() {
17 std::vector<std::thread> v;
18 for (int n = 0; n < 10; ++n) {
19
v.emplace_back(count, n); //runs constructor of std::thread
20 }
21 for (auto& t : v) {
22
t.join();
23 }
24 std::cout << ctr << std::endl;
25 }
19
Hardware Mutual Exclusion
• Advantages:
• Works for single and multi-core processors sharing main memory.
• Simple implementation and simple usage
• Can be used to support multiple critical sections
• Disadvantages:
• Atomic operations are busy-waiting
• Consume CPU time
• Starvation is possible
• In case where more than one process is waiting
20
Condition Variables
• Used in cases where a condition must be met before proceeding.
• Basically: The shared object between two threads and used for wait/notify
• If the condition is not met:
• wait() is initiated.
• A thread is blocked until condition holds
• One blocked, the thread must release all locks
• Once the condition is met:
•
•
•
•
notify() is initiated
A blocked thread un-blocks
Tries to acquire locks before proceeding (might get blocked doing so!)
Checks if the condition is still intact
• Proceeds if so, otherwise blocks again.
21
C++14 Condition Variable API
• #include <condition_variable>
• Wait Functions:
• wait (wait until notified)
• wait_for (wait for timeout or until notified)
• wait_until (wait until notified or time point)
• Notify Functions:
• notify_one (awakes one waiting thread, undefined who!)
• notify_all (awakes all waiting threads)
22
Using C++14 Condition Variable
• #include <condition_variable>
• Thread intending to modify a condition variable must:
• Lock the condition variable (using a mutex)
• Perform modification while holding lock
• Notifies other thread(s) after the change
• Thread intending to wait on the condition variable must:
• Lock the condition variable (using same mutex)
• Waits on the condition variable
• Once notified:
• Re-acquire lock on the condition variable
• The condition variable is checked again, proceeds if condition is fulfilled.
23
C++14 Condition Variable Example
1
2
3
4
5
6
7
8
9
10
11
12
#include <mutex>
#include <condition_variable>
#include <queue>
template <typename T>
class Queue
{
...
std::queue<T> queue;
std::mutex mutex;
std::condition_variable cond;
};
24
pop()
1 T pop()
2 {
3
std::unique_lock<std::mutex> mlock(mutex); //locks scope!
4
while (queue.empty())
5
{
6
cond.wait(mlock);
7
}
8
auto item = queue.front();
9
queue.pop();
10
return item;
11 }
25
push()
1 void push(const T& item)
2 {
3 std::unique_lock<std::mutex> mlock(mutex);
4 queue.push(item);
5 mlock.unlock();
6 cond.notify_one();
7 }
26
Java ReadWriteLock Interface
• Allows multiple read access but single write access!
• Improves performance with large amount of read access and much less access by
writers.
• Granting “read” access:
• No threads are waiting
• No threads requested write access
• Granting “write” access:
• No threads are reading
• No threads are writing
• How is this done?
• Using two locks!
• readLock()
• writeLock()
27
Issues with ReadWriteLock
• Wakes up all waiting threads whenever the lock becomes available
• Wasteful – it is enough one reading thread wants access to stop all writers from
accessing
• We do still want to wake up all reader threads to allow concurrent access
• notify() is not a good solution
• No fairness. Readers starve writers
• Constant flow of readers requires writers to wait for a long time
• Locks aren't dealt out in the order they are requested
• To solve this, a queue is required
• Open Issues:
• Promoting read lock to write lock?
• Demoting write lock to read lock?
28
Practical Session 1&2
• Java8 Concurrency:
• Guide 1, StampedLock
• C++11 Concurrency:
• Guide 1: Link 1, Link 2, Link 3, Link 4
• C++14 Concurrency:
• Guide 1
• Assignment 1 (Part 1)
• C++: std::chorno, std::thread, std::thread::yield, std::condition_variable
• Java: Java7/8 mutual exclusion tools
• StampedLock vs ReaderWriterLock vs Synchronized
• Performance? When to use what? Why? readme!
• What is lock contention? readme!
• Java8 vs C++14
• Differences in synchronization tools? Which language doesn’t have what? Why?
29
Thank you for coming!
30
© Copyright 2026 Paperzz