From 38fdf65a72796284a396605c0779e681f656947f Mon Sep 17 00:00:00 2001 From: Faraz Ardeh Date: Thu, 25 Jun 2026 13:22:14 +0330 Subject: [PATCH] =?UTF-8?q?Assignment=209=20=E2=80=93=20Theoretical=20Ques?= =?UTF-8?q?tions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 8196 bytes Theoretical Questions.tex | 498 -------------------------------------- 2 files changed, 498 deletions(-) delete mode 100644 Theoretical Questions.tex diff --git a/.DS_Store b/.DS_Store index ea7b98eaaf5bc00e448fcca0e442e288738c536a..2c3cd83ab88de87b74da7bac313cfbdb836439a3 100644 GIT binary patch literal 8196 zcmeHM%}*0S6n_H+T2Wb`AS9S~2AbhyDS6 zUet?!fCm%rUQ9e_OpI4O7|x#LWa7;?J5##bmc$bnoJnSWFZ14;-}LouXWtY6NTQmb z0vG{+Fm{GYFAi-Q)3e^vI>ZcfCg=}Pg*@nHaVo9R7Hc^nBOoInBOoInBOoL2FGK*( zY}UeiocmlWM;QSbf&VfB?Ds*%&M+>p#?kxfz`-j45JNcaHI%Uq@O)H(ae+0Co-3}& zs|O-ni6}7$cSpU-%n{=PYaF>d5bh2{FcVRs5RV=GRL>kpoTD6N1Y`tSBfx6+9ykXY zn2`0?@8yaxe%xwNrpaW^q&d`tpZ%Dfd-&_aOgpRmUszSv*lj_b&3k%7xSt{z;PRUU zp^KjXf*iX#{~o%@&Wi_p4?cZ^y`4V5JN^WhhA!ka*bN3`1I#e?DX8^xzs_uz9&v7; zf0;Je+gk+qp~m<-!2ugILtC%#=#tO5ifK6R zw0~%5X!yj)=)6W(&Owys;=dh>;Xqq`ZOyt1nFIhsvPbAdXBYfx+Drn5#jQm$T3 zSp|xW>xRBDUtua|S+^a;7N5*Y*0!@&QBQ2%GP60|oVOCxb!nMdveh(dE!a9+DPy3_ zK1*zRhs6#c7N4sNr@5d&j0*sfXk8LWV($v7Coh!@D<6r(;)_BRh6$K~tFR0u*n+3< z5?;Yu_z2(OC;TQoWR#4P337p4B-7*yNs;SBBWuJUce#IuIHBOASWf_PP!L~nFukTPR!_aa8vX>_%O;S99dD2(m~c(W$n>%uLCH djyQvD-!B3huc(Lrbu(~w%k$q$I5~I`_ydKa00jU5 delta 204 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$SATgU^g?P$YvgaNsIzH>4w3{ z`MCu^1wi0EdGbVIG1kCfvEH7^YlJ1CtW(0$ObiT@pNYyTn<@d7u`!e|Br+5+q%)*4 zl;q~SxFqG|CjrGd{>rA8^iEb6lV^pnyu}n58#Wf!FfL~2;1Fa6SpjqmH;`}zIcQ_y acjn3bGM*skF#v4nnKx diff --git a/Theoretical Questions.tex b/Theoretical Questions.tex deleted file mode 100644 index cfeaa6a..0000000 --- a/Theoretical Questions.tex +++ /dev/null @@ -1,498 +0,0 @@ -\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}