8.1 State Space Diagrams
A state space diagram is the primary tool for verifying correctness properties (mutual exclusion, deadlock-freedom, starvation-freedom) of concurrent protocols with a small number of threads and shared variables.
Mutual Exclusion (in terms of states)
A protocol satisfies mutual exclusion if no reachable state has both threads simultaneously in their critical section. Equivalently, the state does not appear in .
Deadlock
A state is a deadlock if it has no outgoing edges, i.e., no thread can make progress, yet at least one thread has not finished (is not in the non-critical section). A protocol is deadlock-free if no reachable state is a deadlock.
Starvation
A protocol suffers from starvation if there exists an infinite execution path (an infinite sequence of states in the diagram) along which some thread that is trying to enter its critical section never does so.
A protocol is starvation-free if every thread that attempts to enter the critical section eventually succeeds.
Each node of the state space diagram is a “node”:
System State
A state of a concurrent protocol is a tuple listing the current program location of each thread together with the current values of all shared variables. For two threads and with shared variables , a state has the form
where denotes that thread is about to execute line , and similarly for .
Example: from the exercise sheet 9

8.1.1 Reducing the State Space
In practice, the full state space is exponentially large. The standard reduction is:
- Collapse all program locations that are observationally equivalent with respect to the shared-variable values relevant to the protocol → merge into one abstract node.
- Only retain transitions involving the pre-protocol, critical section, and post-protocol steps.
8.2 Locks
Atomic Register
A register supports operations and . It is atomic if every invocation takes effect at a single point in time satisfying:
- lies between the start and end of ,
- any two operations on the same register have distinct effect times,
- returns the value written by the with the largest strictly before .
In Java: volatile primitives and AtomicInteger / AtomicIntegerArray give atomic registers.
Events and Precedence
Thread produces a sequence of events . The -th occurrence of event is written . We write if event occurs before event . Within a single thread, is a total order.
Interval and Concurrent Intervals
An interval is a pair of events with . For intervals and we write if (” precedes ”). If neither nor , the intervals are concurrent.
8.2.1 Two-Process Locks
We consider two processes and sharing volatile variables. Each attempt below reveals a different failure mode.
1st Try — Wrong Order of Flag and Wait
volatile boolean wantp = false, wantq = false;
P: while (wantq); // wait
wantp = true; // announce intent
CS_P
wantp = false;
Q: while (wantp);
wantq = true;
CS_Q
wantq = false;
Both threads can pass the while before either sets its own flag, reaching and simultaneously. Mutual exclusion is violated.

2nd Try — Announce Then Wait
volatile boolean wantp = false, wantq = false;
P: wantp = true; // announce first
while (wantq); // then wait
CS_P
wantp = false;
If both announce before either checks, both spin forever waiting for the other to retract. Deadlock.

3rd Try — Turn Variable Alone
volatile int turn = 1;
P: while (turn != 1); CS_P turn = 2;
Q: while (turn != 2); CS_Q turn = 1;
Mutual exclusion and deadlock-freedom hold, but if stalls outside its CS (allowed by our assumptions), can never re-enter once it sets turn = 2. Starvation.

8.2.2 Dekker’s Algorithm
Dekker combines tries 2 and 3: each process announces intent and yields via a turn variable only when there is a conflict.
volatile boolean wantp = false, wantq = false;
volatile int turn = 1;
P:
wantp = true;
while (wantq) {
if (turn == 2) { // Q has preference
wantp = false; // let Q proceed
while (turn != 1); // and wait
wantp = true; // then try again
}
}
CS_P
turn = 2;
wantp = false;
is symmetric with roles swapped. This is the first correct two-process mutual exclusion algorithm using only atomic registers.
8.2.3 Peterson’s Lock
A cleaner solution than Dekker’s, using a single victim variable for conflict resolution.
volatile boolean flag[2] = {false, false};
volatile int victim;
lock(me):
flag[me] = true; // "I am interested"
victim = me; // "but you go first"
while (flag[1-me] && victim == me);
unlock(me):
flag[me] = false;
Key Idea: The victim variable ensures that if both processes try to enter simultaneously, only the one who isn’t the victim can proceed immediately.