Knowledge Guide
HomeConcurrencyConcurrency Foundations

1. Mutex Lock

A mutex turns a multi-instruction read-modify-write into one that no other thread can interleave with, by forcing every thread to acquire exclusive ownership of a single lock object before touching the shared variable and release it only after the write commits — so the OS may still pause a thread mid-update, but no other thread can observe or overwrite the half-finished state.

The experiment

Two threads each run counter++ 100 times. The correct final value is 200. To make the race visible on every run instead of once in a million, we split counter++ into its three real steps — read into temp, pause, write back temp + 1 — and sleep 1 ms in the middle. That sleep widens the window in which one thread holds a stale temp while the other writes, which is exactly the window a mutex must close.

public class Solution {

  private static int counter = 0;
  private static final Object lock = new Object();

  public static void runExperiment(String name, Runnable task) {
    counter = 0;
    Thread t1 = new Thread(task);
    Thread t2 = new Thread(task);
    t1.start(); t2.start();
    try { t1.join(); t2.join(); }
    catch (InterruptedException e) { Thread.currentThread().interrupt(); }
    System.out.println("Final counter (" + name + "): " + counter);
  }

  // CORRECT: read-pause-write is one indivisible critical section.
  static void withMutex() {
    for (int i = 0; i < 100; i++) {
      synchronized (lock) {            // monitorenter on `lock`
        int temp = counter;            //   read
        sleepOneMs();                  //   pause (still holding the lock)
        counter = temp + 1;            //   write
      }                                // monitorexit on `lock`
    }
  }

  // BUGGY: nothing protects the read-pause-write triple.
  static void noMutex() {
    for (int i = 0; i < 100; i++) {
      int temp = counter;              // read  (may already be stale)
      sleepOneMs();                    // pause
      counter = temp + 1;              // write (clobbers the other thread)
    }
  }

  static void sleepOneMs() {
    try { Thread.sleep(1); }
    catch (InterruptedException e) { Thread.currentThread().interrupt(); }
  }

  public static void main(String[] args) {
    runExperiment("with mutex", Solution::withMutex);  // always 200
    runExperiment("no mutex",   Solution::noMutex);    // typically ~100..120
  }
}

Why the naive version is wrong — traced interleaving

Take just the very first iteration of both threads with counter == 0. Without the mutex, the OS scheduler is free to run them in this order (the 1 ms sleep makes this the likely order, not a rare one):

StepThread AThread Bcounter
1temp = counter → A's temp = 00
2sleep(1) — A pausestemp = counter → B's temp = 00
3still sleepingsleep(1) — B pauses0
4counter = temp + 1 → writes 1still sleeping1
5counter = temp + 1 → writes 11

Two increments happened; counter is 1, not 2. B read 0 before A's write landed, so B's write at step 5 silently overwrites A's. This is a lost update. Repeated across 200 increments, the lost ones drag the total down to roughly 100–120 — almost never 200, and a different wrong number each run.

The mutex version cannot reach step 2: B's synchronized (lock) blocks until A's block finishes, so A's read, pause, and write run as an unbroken unit. B then reads counter == 1 and writes 2. Both increments are preserved.

What synchronized (lock) actually compiles to

The keyword is not magic — javac emits two JVM bytecodes around the block: monitorenter on entry and monitorexit on exit (plus a hidden second monitorexit on an exception edge, so the lock is released even if the body throws). Every Java object has an associated monitor: a hidden owner field plus a queue of waiting threads.

Crucially, monitorenter/monitorexit also impose a happens-before edge under the Java Memory Model: everything a thread wrote before releasing the lock is visible to the next thread that acquires it. So the mutex fixes two problems at once — mutual exclusion (no interleaving) and visibility (no stale cached counter).

diagram
diagram

Go: the same logic with sync.Mutex (and the channel alternative)

Go threads are goroutines — lightweight, multiplexed by the Go runtime onto a small pool of OS threads (the M:N scheduler), so spawning thousands is cheap, unlike Java's one-OS-thread-per-Thread model. The lock itself is analogous: sync.Mutex's Lock()/Unlock() play the role of monitorenter/monitorexit, and defer mu.Unlock() is the idiomatic equivalent of the JVM's exception-safe release.

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var mu sync.Mutex
	var wg sync.WaitGroup
	counter := 0

	increment := func() {
		defer wg.Done()
		for i := 0; i < 100; i++ {
			mu.Lock()            // like monitorenter
			temp := counter      // read
			time.Sleep(time.Millisecond) // pause, still holding lock
			counter = temp + 1   // write
			mu.Unlock()          // like monitorexit
		}
	}

	wg.Add(2)
	go increment()
	go increment()
	wg.Wait()
	fmt.Println("Final counter:", counter) // always 200
}

Go's motto is "share memory by communicating, not communicate by sharing memory." The channel version owns the counter in one goroutine and sends deltas to it — no lock at all, because only one goroutine ever touches the variable:

func main() {
	deltas := make(chan int)
	done := make(chan int)

	go func() { // sole owner of `counter`
		counter := 0
		for d := range deltas {
			counter += d
		}
		done <- counter
	}()

	var wg sync.WaitGroup
	wg.Add(2)
	for w := 0; w < 2; w++ {
		go func() {
			defer wg.Done()
			for i := 0; i < 100; i++ { deltas <- 1 }
		}()
	}
	wg.Wait()
	close(deltas)
	fmt.Println("Final counter:", <-done) // 200
}

Java's nearest analogue to channels is a BlockingQueue plus wait/notify; Go bakes the queue and the blocking handoff into the language as a first-class channel. Pick the mutex when threads genuinely share one piece of state; pick the channel when you can give a single owner exclusive responsibility for it.

Pitfalls

Takeaways


Sources: Brian Goetz et al., Java Concurrency in Practice (ch. 2–3, on atomicity, locking, and happens-before); the JVM Specification §6.5 (monitorenter/monitorexit semantics) and JLS §17.4 (Java Memory Model); the Go Memory Model and Effective Go (goroutines, sync.Mutex, channels, "share memory by communicating"). Re-authored and deepened for this guide: added the traced first-iteration interleaving, the monitorenter/monitorexit compilation note, a with-vs-without diagram, a "why the naive version is wrong" explanation, and the Go mutex + channel counterparts.

🤖 Don't fully get this? Learn it with Claude

Stuck on 1. Mutex Lock? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **1. Mutex Lock** (Concurrency) and want to truly understand it. Explain 1. Mutex Lock from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **1. Mutex Lock** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **1. Mutex Lock** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **1. Mutex Lock** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes