Interview Prep Zoneby CuriouserLabs

Question 2 of 5 · senior level

Mutex vs semaphore vs spinlock — mechanics, when spinning beats blocking, and what a futex is.

🎤 Say this first

A mutex is ownership: one holder, only the holder unlocks — protects critical sections. A semaphore is a counter with wait/signal: no ownership, counts permits — bounds resources (pool of N) or signals events between threads. A spinlock never sleeps: it burns CPU retrying CAS — correct choice only when the hold time is shorter than a context switch (~µs) and the holder can't be preempted (kernel interrupt paths); in user space, raw spinning is usually wrong. Linux mutexes are futexes: uncontended lock/unlock is one CAS in user space — the kernel is only involved when there's actual contention.

The full picture

MutexSemaphoreSpinlock
Semanticsownership, exclusivecounter of permitsexclusive, busy-wait
Blocked waitersleeps (yields CPU)sleepsspins (burns CPU)
Right whencritical sectionsbounding N resources; signalingsub-µs holds, no preemption (kernel)
Java facesynchronized / ReentrantLockSemaphore; also BlockingQueue permitsspin phase of adaptive locks
Cost profileCAS if uncontended; futex sleep if notsamezero latency to acquire, CPU waste while waiting
  • The futex insight (worth stating explicitly): 'locks are syscalls' is false on Linux since 2003 — fast path is pure user-space CAS; futex(FUTEX_WAIT) only on contention. That's why uncontended synchronized is nearly free (JVM biased/thin-lock history is the same idea one layer up).
  • Adaptive/hybrid locks: spin briefly (maybe the holder finishes in 200ns), then sleep — best of both; this is what JVM monitors and pthread adaptive mutexes actually do. Pure spinning in user space fails catastrophically when the holder gets descheduled: you spin your whole quantum against a sleeping owner.
  • Semaphore as signaling: init 0, worker waits, producer signals — a one-shot event; this no-ownership property is exactly why semaphores can be released by a different thread than acquired (and why that's also their footgun).
  • Bounded-resource idiom: Semaphore(50) in front of a downstream is the concurrency-limit pattern that replaced pool-size-as-limiter in the virtual-thread era — an OS primitive solving a 2026 microservice problem.

🔄 Likely follow-up questions

  • Walk through the futex fast/slow path for lock and unlock.
  • Why is spinning acceptable in kernel interrupt context but sinful in user space?
  • How does a BlockingQueue combine a mutex, conditions and effectively two semaphores?