\documentclass[12pt,a4paper]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage[margin=2.5cm]{geometry} \usepackage{listings} \usepackage{xcolor} \usepackage{hyperref} \usepackage{parskip} \usepackage{booktabs} \usepackage{array} \usepackage{titlesec} \usepackage{fancyhdr} \usepackage{amsmath} \usepackage{enumitem} % ── Page style ────────────────────────────────────────────────────────────── \setlength{\headheight}{14.5pt} \pagestyle{fancy} \fancyhf{} \rhead{Advanced Programming – Assignment 9} \lhead{Faraz Ardeh} \cfoot{\thepage} % ── Section formatting ─────────────────────────────────────────────────────── \titleformat{\section}{\large\bfseries}{Question \thesection.}{0.6em}{} \titleformat{\subsection}{\normalsize\bfseries}{}{0em}{} % ── Java code style ───────────────────────────────────────────────────────── \definecolor{javakw}{rgb}{0.13,0.13,0.60} \definecolor{javastr}{rgb}{0.63,0.13,0.13} \definecolor{javacmt}{rgb}{0.38,0.38,0.38} \definecolor{codebg}{rgb}{0.97,0.97,0.97} \definecolor{codefr}{rgb}{0.82,0.82,0.82} \lstdefinestyle{java}{ language=Java, basicstyle=\ttfamily\small, keywordstyle=\color{javakw}\bfseries, stringstyle=\color{javastr}, commentstyle=\color{javacmt}\itshape, numberstyle=\tiny\color{gray}, numbers=left, stepnumber=1, numbersep=8pt, backgroundcolor=\color{codebg}, frame=single, rulecolor=\color{codefr}, breaklines=true, breakatwhitespace=false, showstringspaces=false, tabsize=4, captionpos=b, } \lstset{style=java} % ── Inline code ───────────────────────────────────────────────────────────── \newcommand{\code}[1]{\texttt{\small #1}} % ──────────────────────────────────────────────────────────────────────────── \begin{document} % ── Title page ─────────────────────────────────────────────────────────────── \begin{titlepage} \centering \vspace*{3cm} {\Huge\bfseries Advanced Multithreading\par} \vspace{0.8cm} {\large Assignment 9 – Theoretical Questions\par} \vspace{0.4cm} {\large Advanced Programming (AP)\par} \vspace{2cm} \rule{0.6\linewidth}{0.5pt}\\[0.4cm] {\large\bfseries Faraz Ardeh\par} \vspace{0.3cm} {\normalsize fa.ardeh@gmail.com\par} \vspace{0.6cm} {\normalsize Shahid Beheshti University\par} \vspace{0.3cm} {\normalsize June 2026\par} \vfill \end{titlepage} \tableofcontents \newpage % ════════════════════════════════════════════════════════════════════════════ \section{What Are Atomic Variables?} % ════════════════════════════════════════════════════════════════════════════ An \textbf{atomic variable} is a variable whose read-modify-write operations are executed as a single, indivisible step -- no other thread can observe an intermediate state during the operation. \subsection{How they differ from ordinary variables} With a normal variable, the innocent-looking increment \code{i++} is actually \emph{three} separate CPU instructions: \begin{enumerate}[leftmargin=2em] \item \textbf{Read} the current value of \code{i} into a register. \item \textbf{Add 1} to that register value. \item \textbf{Write} the result back to \code{i}. \end{enumerate} If two threads execute \code{i++} concurrently, both may read the same value, both add 1, and both write back the same result -- one increment is silently lost. This is a \emph{race condition}. An atomic variable uses a hardware-level instruction called \textbf{Compare-And-Swap (CAS)}, which reads, compares, and writes in a single CPU operation that cannot be interrupted. If another thread changed the value in between, the CAS fails and the operation retries -- but no update is ever lost. \subsection{Key difference summary} \begin{center} \begin{tabular}{>{\bfseries}lll} \toprule Property & Ordinary variable & Atomic variable \\ \midrule Thread-safe reads/writes & No & Yes \\ Compound operations (e.g.\ \code{i++}) & Not atomic & Atomic (CAS) \\ Requires explicit lock & Yes & No \\ Performance under low contention & Faster & Comparable \\ \bottomrule \end{tabular} \end{center} % ════════════════════════════════════════════════════════════════════════════ \section{Classes from \code{java.util.concurrent.atomic}} % ════════════════════════════════════════════════════════════════════════════ The \code{java.util.concurrent.atomic} package provides lock-free, thread-safe operations for a range of data types. Four commonly used classes are listed below. \begin{center} \begin{tabular}{>{\bfseries}ll} \toprule Class & What it wraps \\ \midrule \code{AtomicInteger} & A single \code{int} value \\ \code{AtomicLong} & A single \code{long} value \\ \code{AtomicBoolean} & A single \code{boolean} flag \\ \code{AtomicReference} & A reference to any object of type \code{V} \\ \bottomrule \end{tabular} \end{center} \subsection{Typical use case -- \code{AtomicInteger} as a shared request counter} Imagine a web server where many threads handle HTTP requests simultaneously. All of them must increment a shared request counter. Using \code{AtomicInteger} avoids a \code{synchronized} block while still guaranteeing that every request is counted exactly once: \begin{lstlisting}[caption={Shared request counter with AtomicInteger}] import java.util.concurrent.atomic.AtomicInteger; public class RequestCounter { private final AtomicInteger count = new AtomicInteger(0); public void handleRequest() { int id = count.incrementAndGet(); // atomic: no lock needed System.out.println("Handling request #" + id); } public int getTotal() { return count.get(); } } \end{lstlisting} Because \code{incrementAndGet()} is a single atomic CAS instruction, hundreds of threads can call it simultaneously without any of them ever seeing a stale or lost count. % ════════════════════════════════════════════════════════════════════════════ \section{Locks vs.\ Atomic Variables} % ════════════════════════════════════════════════════════════════════════════ Both locks and atomic variables protect shared data from concurrent access, but they are suited to different scenarios. \subsection{When a lock is the better choice} \begin{itemize}[leftmargin=2em] \item \textbf{Multiple variables must stay consistent together.} Atomic variables protect exactly one value at a time. If you need to update an account's \code{balance} and \code{lastModified} timestamp as one unit, only a lock can guard both fields atomically. \item \textbf{Complex conditions and waiting.} Locks support \code{Condition} objects (\code{await}/\code{signal}), letting a thread block until some business invariant is satisfied (e.g.\ ``wait until the queue is non-empty''). Atomic variables provide no such mechanism. \item \textbf{Long critical sections.} If the guarded work is more than a single read-modify-write (e.g.\ several method calls or I/O), a lock is the natural fit. CAS-based retry loops become expensive when the critical section is long, because failed retries waste CPU cycles. \end{itemize} \subsection{When an atomic variable is the better choice} \begin{itemize}[leftmargin=2em] \item \textbf{Single-variable, simple operations.} Counters, flags, and version numbers that are updated with one CAS have lower overhead than acquiring and releasing a lock. \item \textbf{High-read, low-write scenarios.} \code{AtomicReference} allows a single writer to publish a new immutable snapshot while many readers access the current reference without any blocking. \item \textbf{Non-blocking algorithms.} Lock-free data structures (e.g.\ \code{ConcurrentLinkedQueue}) use CAS to guarantee progress even if one thread is delayed, whereas a lock-based structure can stall all other threads if the lock holder is descheduled. \end{itemize} \begin{center} \begin{tabular}{>{\bfseries}lll} \toprule Situation & Better choice & Reason \\ \midrule Single counter / flag & Atomic variable & Low overhead, no blocking \\ Multiple fields as one unit & Lock & Atomicity across several variables \\ Conditional waiting & Lock & \code{Condition.await()} / \code{signal()} \\ Long critical section & Lock & CAS retries too costly \\ Non-blocking read-heavy & Atomic variable & Readers never block \\ \bottomrule \end{tabular} \end{center} % ════════════════════════════════════════════════════════════════════════════ \section{Correct but Slow -- Scalability Limits Under High Contention} % ════════════════════════════════════════════════════════════════════════════ A program can be completely free of race conditions and still perform poorly when many threads compete for the same resources. Correctness and scalability are independent properties. \subsection{Why this happens} When every thread must pass through the same lock, threads queue up waiting even if they could logically run in parallel. Adding more CPU cores does not help -- the bottleneck is the shared resource, not raw compute power. This matches \textbf{Amdahl's Law}: total speedup is bounded by the fraction of work that \emph{cannot} be parallelised. \subsection{Three concurrency factors that limit scalability} \begin{enumerate}[leftmargin=2em,label=\textbf{\arabic*.}] \item \textbf{Lock contention.} A coarse-grained lock (one lock protecting many accounts, for example) serialises all threads even when they are working on completely independent data. The more threads, the longer each one waits. The solution is to use finer-grained locking -- one lock per account instead of one global lock -- so threads that are not sharing data can proceed concurrently. \item \textbf{False sharing (cache-line contention).} Modern CPUs cache memory in 64-byte \emph{cache lines}. If two threads update logically unrelated variables that happen to reside on the same cache line, each write by one thread invalidates the other core's cached copy. The hardware constantly transfers the cache line between cores -- a hidden performance cost that grows with thread count even though there is no logical data sharing between the threads. \item \textbf{Memory bandwidth saturation.} With many threads all reading and writing shared state, the memory bus becomes a bottleneck. Once the bus is saturated, adding more threads only increases waiting time -- each thread must wait longer for its memory access to be serviced. Throughput plateaus or even decreases despite the increase in CPU utilisation. \end{enumerate} % ════════════════════════════════════════════════════════════════════════════ \section{Why More Threads Do Not Always Improve Performance} % ════════════════════════════════════════════════════════════════════════════ Intuitively, more threads should mean more parallelism and faster execution. In practice, beyond a certain point additional threads hurt rather than help. \subsection{Context switching} When the number of threads exceeds the number of CPU cores, the operating system must \emph{time-slice}: it saves the full register state of one thread and loads the state of another many times per second. Each switch has a non-trivial cost (typically a few microseconds). With hundreds of threads competing for a small number of cores, the CPU can spend a significant fraction of its time doing bookkeeping instead of useful work. \subsection{Lock contention} More threads means more threads competing for the same lock simultaneously. Each additional thread increases the average queue length at every lock, so threads spend proportionally more time \emph{blocked} and less time running. In extreme cases, throughput actually decreases because the contention overhead outweighs any benefit from concurrency. \subsection{Cache coherence} Every CPU core maintains its own L1/L2 cache. When one core writes to a shared variable, all other cores that cached that value must \emph{invalidate} their copy (the MESI protocol). With many threads spread across many cores, this cache-invalidation ``chatter'' becomes expensive: cores stall waiting for the authoritative copy to arrive from another core's cache or from main memory. \subsection{Synchronization overhead} Every \code{lock()}/\code{unlock()} pair, every \code{synchronized} block, and every \code{volatile} write inserts a \emph{memory fence} instruction. Memory fences force the CPU to flush its store buffers and ensure all prior writes are visible to other cores before proceeding. The more threads, the more synchronisation events per unit of time, and the more time spent on coordination rather than computation. \subsection{Summary} \begin{center} \begin{tabular}{>{\bfseries}ll} \toprule Factor & Effect \\ \midrule Context switching & CPU wastes time saving/restoring thread state \\ Lock contention & Threads queue up; throughput flattens or drops \\ Cache coherence & Cores stall waiting for invalidated cache lines \\ Sync overhead & Memory fences cost cycles per lock/volatile operation \\ \bottomrule \end{tabular} \end{center} % ════════════════════════════════════════════════════════════════════════════ \section{Why Deadlocks Appear in Production but Not During Testing} % ════════════════════════════════════════════════════════════════════════════ \subsection{A thread-scheduling perspective} A deadlock requires a very specific simultaneous state: Thread~A holds lock~1 and waits for lock~2, while Thread~B holds lock~2 and waits for lock~1 -- at the \emph{same instant}. This is called a \emph{circular wait}. In a typical test environment: \begin{itemize}[leftmargin=2em] \item The workload is small (few threads, few iterations). \item The JVM is freshly started with warm-up behaviour. \item The OS scheduler tends to run the same thread continuously for short tasks, so the critical interleaving rarely occurs. \end{itemize} In production: \begin{itemize}[leftmargin=2em] \item There are many more threads running concurrently. \item Higher load means more context switches per second. \item Different hardware (more cores, different cache sizes) produces different scheduling patterns. \item The combination of load, hardware, and OS scheduler eventually \emph{will} hit the exact timing that triggers the circular wait, making it a statistical certainty over hours of operation. \end{itemize} \subsection{Two strategies to expose deadlocks during testing} \begin{enumerate}[leftmargin=2em,label=\textbf{\arabic*.}] \item \textbf{Stress testing with a start-gun latch.} Create many more threads than CPU cores (e.g.\ 50--100 threads for a 4-core machine), use a \code{CountDownLatch} to hold them all back, then release them simultaneously with a single \code{countDown()}. Every thread starts at the same instant, maximising contention and forcing the scheduler to interleave lock acquisitions in ways that almost never happen under light load. A deadlock that requires simultaneous lock attempts becomes statistically likely within seconds. This is exactly the technique used in the provided \code{BankAccountTransferDeadlockTest}: \begin{lstlisting}[caption={Stress test with a start-gun latch}] CountDownLatch startGun = new CountDownLatch(1); // ... submit 50 000 tasks that each await(startGun) before transferring startGun.countDown(); // all tasks released at once \end{lstlisting} \item \textbf{Inject random delays between lock acquisitions.} Insert a short \code{Thread.sleep(randomMillis)} between acquiring the first lock and trying to acquire the second. This \emph{widens} the window during which another thread can step in, acquire its own first lock (which is the second lock of the first thread), and then attempt the lock that the first thread holds -- the exact condition for a circular wait. Even a 1--5 ms random delay dramatically increases the probability of hitting the deadlock without requiring a huge thread count. This technique is sometimes called \emph{sleep injection} or \emph{timing perturbation}. \end{enumerate} % ════════════════════════════════════════════════════════════════════════════ \section*{Bonus: \code{AtomicInteger} vs.\ Plain \code{int} Race Condition} \addcontentsline{toc}{section}{Bonus: AtomicInteger vs.\ Plain int Race Condition} % ════════════════════════════════════════════════════════════════════════════ The program below creates 100 threads, each incrementing both a plain \code{int} and an \code{AtomicInteger} 1\,000 times. The expected final value is $100 \times 1000 = 100\,000$. \begin{lstlisting}[caption={RaceConditionDemo.java}] import java.util.concurrent.atomic.AtomicInteger; public class RaceConditionDemo { static int unsafeCounter = 0; static AtomicInteger safeCounter = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { int threadCount = 100; int incrementsPerThread = 1000; Thread[] threads = new Thread[threadCount]; for (int i = 0; i < threadCount; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < incrementsPerThread; j++) { unsafeCounter++; // NOT atomic -- race condition! safeCounter.incrementAndGet(); // atomic -- always correct } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); int expected = threadCount * incrementsPerThread; System.out.println("Expected : " + expected); System.out.println("Unsafe int: " + unsafeCounter + (unsafeCounter == expected ? " (correct)" : " <-- LOST UPDATES!")); System.out.println("AtomicInt : " + safeCounter.get() + (safeCounter.get() == expected ? " (correct)" : " <-- wrong!")); } } \end{lstlisting} \subsection*{Sample output} \begin{lstlisting}[language={},numbers=none,backgroundcolor=\color{codebg}] Expected : 100000 Unsafe int: 94371 <-- LOST UPDATES! AtomicInt : 100000 (correct) \end{lstlisting} \subsection*{Why the unsafe counter is wrong} \code{unsafeCounter++} compiles to three bytecode instructions: \code{getfield}, \code{iadd}, \code{putfield}. Between any two of these instructions the thread scheduler can switch to another thread. If two threads both read the same value (say 5\,000), both add 1, and both write back 5\,001, one increment vanishes entirely. Under 100 threads this happens thousands of times, so the final value is typically several thousand less than 100\,000. \code{AtomicInteger.incrementAndGet()} maps to a single \code{lock xadd} CPU instruction (on x86) that is guaranteed to be indivisible. No thread can read an intermediate state; every increment is counted exactly once, and the result is always 100\,000. % ── Footer note ────────────────────────────────────────────────────────────── \vfill \begin{center} \small\color{gray} Advanced Programming – Assignment 9 \quad|\quad Faraz Ardeh \quad|\quad Shahid Beheshti University \quad|\quad June 2026 \end{center} \end{document}