CO2017 — Week3L2 — Synchronisation in Java
CO2017 — Week3L2 — Synchronisation in Java
Dr Gilbert Laycock (gtl1)
2017–02–05
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 1 / 16
Recap/overview
Recap 1: Critical code
Concurrent processes are interleaved unpredictably
Some code sections are critical, and we want to achieve: mutual
exclusion; progress; bounded waiting
Possible techniques considered: disable interrupts; access in
turns; Peterson’s algorithm; semaphores
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 2 / 16
Recap/overview
Recap2: Java threads
Any Java program has one special thread that runs main method
We can define an arbitrary number of additional threads.
Defining a new thread requires two things:
Define a new class implementing interface Runnable (or directly
extend the Thread class)
In function main, define one or more instances of the new
Runnable/Thread class.
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 3 / 16
Recap/overview
Overview
Semaphores in Java
Mutual exclusion with semaphores
Producer/consumer example
Implement producer/consumer example using semaphores
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 4 / 16
Recap/overview
Java interface for the Semaphore class
class Semaphore {
private int value;
public Semaphore (int init) { value = init; }
public void Wait()
{
…???…
}
public void Signal() {
…???…
}
}
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 5 / 16
Recap/overview Simple example of mutual exclusion using a semaphore
Mutual Exclusion with Semaphore
class Thread_i extends Thread {
Semaphore mutex;
public Thread_i(Semaphore s) { mutex = s; }
public void run() {
while (true) {
noncritical_i;
mutex.Wait(); critical_i; mutex.Signal();
}
}
}
public class Controller {
public static void main() {
Semaphore s = new Semaphore(1)
Thread_1 t1 = new Thread_1(s);
……
Thread_n tn = new Thread_n(s);
t1.start(); …; tn.start();
}
}
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 6 / 16
Recap/overview Trace of mutual exclusion
How Semaphore Ensures Mutual Exclusion?
Consider three processes t1, t2 and t3
s t1 t2 t3
Initially 1 start start start
t1 executes Wait() 0 start start start
t2 executes Wait() 0 crit blocked start
t3 executes Wait() 0 crit blocked blocked
t1 executes Signal() 0 noncrit crit blocked
t1 executes Wait() 0 blocked crit blocked
…………………….
Once one process enters its critical section, the others must wait
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 7 / 16
Introducing the producer/consumer problem
The Producer and Consumer Problem
Problem description:
a producer repeatedly produces items and places them into a
buffer;
meanwhile a consumer consumes the items one-by-one by taking
from the buffer.
Requirements:
Assume an implementation of a bounded FIFO buffer with operations
place() and take()
The producer may produce a new item only when the buffer is not full
The consumer may consume an item only when the buffer is not empty
All items produced are eventually consumed
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 8 / 16
Introducing the producer/consumer problem Solution overview
Behaviour of the FIFO Buffer
Implementation as an array.
We need NextIn and NextOut to locate where to place an item into
and take an item from the buffer
NextIn and NextOut are incremented one by one. When they reach the
end of the buffer we need roll-over to 0.
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 9 / 16
Introducing the producer/consumer problem Using semphores to control access to the queue
Design of Solution using semaphores
Use a semaphore ItemsReady to record the number of items in the
buffer
The consumer must be blocked on the semaphore ItemsReady when it
is 0, i.e. when the buffer is empty, the consumer cannot proceed.
The initial value of ItemsReady should be 0 (since the queue is empty).
After the producer places an item, it should signal on the semaphore
ItemsReady.
Use a semaphore SpacesLeft to record free spaces in the buffer; its
initial value should be the buffer size.
The producer should be blocked when SpacesLeft is 0.
After the consumer takes an item, it should signal on the semaphore
SpacesLeft.
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 10 / 16
Introducing the producer/consumer problem Implementation of producer/consumer
The Buffer Class
public class Buffer {
public static final int BUFFSIZE = 5;
private int[] _b = new int[BUFFSIZE];
private int _NextIn, _NextOut;
public Buffer() { _NextIn = 0; _NextOut = 0; }
public void place(int item) {
_b[_NextIn] = item;
_NextIn = (_NextIn + 1)%BUFFSIZE;
}
public int take() {
int value = _b[_NextOut];
_NextOut = (_NextOut + 1)%BUFFSIZE;
return value;
}
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 11 / 16
Introducing the producer/consumer problem Implementation of producer/consumer
The Buffer Class (Cont’d)
public static void main(String[] args) {
Semaphore MutEx = new Semaphore(1);
Semaphore ItemsReady = new Semaphore(0);
Semaphore SpacesLeft = new Semaphore(BUFFSIZE);
Buffer buff = new Buffer();
Producer p = new Producer(buff, MutEx,
ItemsReady, SpacesLeft);
Consumer c = new Consumer(buff, MutEx,
ItemsReady, SpacesLeft);
p.start(); c.start();
}
}
gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 12 / 16
Introducing the producer/consumer problem Implementation of producer/consumer
The Producer Class
class Producer extends Thread {
Buffer _b; Semaphore _mx;
Semaphore _ready; Semaphore _spaces;
public Producer (Buffer buff, Semaphore mutEx,
Semaphore itemsReady,
Semaphore spacesLeft) {
_b = buff; _mx = mutEx;
_ready = itemsReady; _spaces = spacesLeft;
}
public void run() {
for (int i=1; i<=100; ++i) { _spaces.Wait(); // wait for spaces _mx.Wait(); // wait for mutual exclusion _b.place(i); // place an item _mx.Signal(); // release mutual exclusion _ready.Signal();// signal the consumer } } } gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 13 / 16 Introducing the producer/consumer problem Implementation of producer/consumer The Consumer Class class Consumer extends Thread { Buffer _b; Semaphore _mx; Semaphore _ready; Semaphore _spaces; public Consumer (Buffer buff, Semaphore mutEx, Semaphore itemsReady, Semaphore spacesLeft) { _b = buff; _mx = mutEx; _ready = itemsReady; _spaces = spacesLeft; } public void run() { int value; for (int i=1; i <= 100; i++) { _ready.Wait(); // wait for items ready _mx.Wait(); // wait for mutual exclusion value = _b.take(); // take an item _mx.Signal(); // release mutual exclusion _spaces.Signal(); // signal the producer System.out.println(value); } } } gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 14 / 16 Introducing the producer/consumer problem Implementation of producer/consumer Be Careful! What if ready.Wait(); mx.Wait() was swapped to mx.Wait(); ready.Wait() ? Answer: Exchanging the two Wait()s is fatal Consumer executes mx.Wait() and ready.Wait() and is blocked Producer executes spaces.Wait() and mx.Wait() and is blocked Deadlock: A process is said to be deadlocked if it is waiting for an event which will never happen gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 15 / 16 Summary Summary A semaphore is a primitive to synchronise threads. A semaphore has Wait() and Signal() operations to guarantee mutual exclusion of threads relevant to one shared object. Demonstrated the application of semaphores for the Producer and Consumer problem. gtl1–R914 W3L2 — Java Synchronisation 2017–02–05 16 / 16