CSC 210 — Concurrent Programming
Test Prep Guide (Saturday → Monday)
Every Java example below was actually compiled and run — the output you see is real, not
guessed. Use this top-to-bottom; by the end you should be able to explain every term and
sketch the code for it on paper.
1. What is Concurrent Programming?
Plain English: Concurrent programming is writing a program where multiple "things"
appear to happen at the same time, instead of one strictly after another.
Think of a restaurant kitchen:
Sequential (non-concurrent): One chef does everything — chops, fries, plates — one
dish at a time, start to finish, before touching the next dish.
Concurrent: Several chefs work in the kitchen at once. One is frying, one is chopping,
one is plating. They share the same kitchen (memory), the same stove (CPU), and
sometimes the same knife (a resource) — so they need rules to avoid chaos.
Why we need it:
Modern CPUs have multiple cores — sequential programs waste that power.
Some tasks involve waiting (network calls, disk reads). While one task waits, another
can use the CPU.
It makes programs feel faster/more responsive (e.g., a UI that doesn't freeze while
downloading a file).
Concurrency vs Parallelism (a common exam trick):
Term Meaning
Concurrency Dealing with many tasks at once — they may take turns on a single core
(interleaved), creating the illusion of simultaneity
Parallelism Tasks literally run at the same physical instant on different CPU cores
Exam one-liner: Concurrency is about structure (multiple tasks in progress);
parallelism is about execution (multiple tasks running simultaneously on multiple
cores).
2. Threads
Plain English: A thread is the smallest unit of execution inside a program — a single path
through your code that can run independently of other paths. Every Java program has at
least one thread automatically: the main thread.
A process (your whole running program) can contain many threads, and they all share the
same memory space (heap), but each thread has its own stack and program counter.
Three ways to create a thread in Java
java
public class ThreadCreation {
// Method 1: Extend the Thread class
static class MyThread extends Thread {
public void run() {
[Link]("[Extends Thread] Running on: " + [Link]
}
}
// Method 2: Implement Runnable
static class MyRunnable implements Runnable {
public void run() {
[Link]("[Implements Runnable] Running on: " + [Link]
}
}
public static void main(String[] args) throws InterruptedException {
[Link]("Main thread is: " + [Link]().getName(
// Method 1
MyThread t1 = new MyThread();
[Link]();
[Link]();
// Method 2
Thread t2 = new Thread(new MyRunnable());
[Link]();
[Link]();
// Method 3: Lambda (Runnable is a functional interface)
Thread t3 = new Thread(() ->
[Link]("[Lambda Runnable] Running on: " + [Link]
);
[Link]();
[Link]();
}
}
Output:
Main thread is: main
[Extends Thread] Running on: Thread-0
[Implements Runnable] Running on: Thread-1
[Lambda Runnable] Running on: Thread-2
Key vocabulary to remember:
start() — tells the JVM to schedule the thread to run (don't call run() directly —
that just runs the code on the current thread, no new thread is created!)
join() — makes the calling thread wait until the other thread finishes
Runnable is generally preferred over extending Thread , because Java only allows
single inheritance, but a class can implement multiple interfaces. Implementing
Runnable also separates "the task" from "the thing that runs the task."
3. Multithreading
Plain English: Multithreading is simply the technique/practice of using multiple threads
within one program. "Thread" is the unit; "multithreading" is the strategy of using many of
them together to do work concurrently.
It's the difference between knowing what a "chef" is (a thread) and running a kitchen with 5
chefs working together (multithreading).
Why it matters for exams: lecturers often ask "what is multithreading and why is it used?"
— answer: running multiple threads within a single process so independent tasks can make
progress concurrently, improving responsiveness and CPU utilization.
4. Thread Pools
Plain English: Creating a brand-new Thread object every time you need to do a small task
is expensive (it takes real OS resources to create and destroy a thread). A thread pool is a
fixed group of worker threads that are created once and reused for many tasks.
Analogy: instead of hiring and firing a new chef for every single dish (expensive), you keep
a permanent staff of 3 chefs who pick up new orders as they finish old ones.
In Java, thread pools are managed through the ExecutorService interface (package
[Link] ).
java
import [Link];
import [Link];
import [Link];
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
// A pool of 3 reusable worker threads.
ExecutorService pool = [Link](3);
for (int i = 1; i <= 6; i++) {
int taskId = i;
[Link](() -> {
[Link]("Task " + taskId + " running on " + Thread.c
});
}
[Link](); // stop accepting new tasks
[Link](5, [Link]); // wait for all to finish
[Link]("All tasks finished.");
}
}
Output (notice only 3 thread names appear for 6 tasks — they're being reused!):
Task 2 running on pool-1-thread-2
Task 4 running on pool-1-thread-2
Task 5 running on pool-1-thread-2
Task 6 running on pool-1-thread-2
Task 3 running on pool-1-thread-3
Task 1 running on pool-1-thread-1
All tasks finished.
Your exact ordering may differ if you run it again — that's expected and is itself a
concurrency concept (non-determinism of thread scheduling). What should not
change is that only 3 distinct thread names appear.
Common pool types to know by name: newFixedThreadPool(n) , newCachedThreadPool() ,
newSingleThreadExecutor() , newScheduledThreadPool(n) .
5. Benefits of Threads
1. Better CPU utilization — while one thread waits (I/O, network, disk), another can use
the CPU.
2. Responsiveness — e.g., a GUI app stays interactive while a background thread loads a
file.
3. Faster execution for parallelizable work — splitting a large task across cores can
finish it sooner.
4. Resource sharing within a process — threads in the same process can share data
directly through memory (faster than processes talking to each other).
5. Simpler program structure in some designs — e.g., a server can handle each client
connection on its own thread instead of one giant tangled loop.
6. Risks of Threads
1. Race conditions — two threads modify shared data at the same time, causing lost or
corrupted updates (full demo in Section 9).
2. Deadlock — two or more threads wait forever for each other's locks (Section 13).
3. Livelock — threads keep changing state in response to each other, but none of them
ever actually makes progress (Section 13).
4. Starvation — a thread is repeatedly denied access to a resource because other threads
keep "cutting in line."
5. Increased complexity — bugs from threading are notoriously hard to
reproduce/debug because they depend on timing, which changes from run to run.
6. Overhead — creating too many threads wastes memory and CPU on context-
switching.
7. Visibility problems — one thread's update to a shared variable might not be seen by
another thread immediately, due to CPU caching, unless properly synchronized (this
is what volatile and synchronized help fix).
7. Thread Synchronization
Plain English: Synchronization is the set of techniques used to control how multiple
threads access shared data, so they don't interfere with each other.
The core problem: when a piece of shared data can be read/written by more than one
thread, you need a rule for "whose turn is it?" — otherwise you get race conditions.
Java's basic tool: the synchronized keyword. It enforces mutual exclusion — only one
thread may execute a synchronized block/method on a given object's lock at a time. Every
object in Java has a hidden internal lock called a monitor, and synchronized uses that lock.
java
static synchronized void increment() {
// only ONE thread can be inside here at a time, for a given object
}
8. Different Ways to Synchronize State
Technique What it does When to use
synchronized method/block Only one thread inside at Simple, most common
a time (uses an object's case
built-in monitor lock)
volatile keyword Guarantees visibility of a Simple flags / single
variable's latest value reads-writes, not
across threads (does not counters
give atomicity for
compound operations
like i++ )
[Link].* Lock-free atomic read- Counters, flags, simple
( AtomicInteger , AtomicLong , etc.) modify-write operations atomic updates —
using CPU-level usually faster than
instructions synchronized
[Link] (e.g. Explicit lock you When you need finer
ReentrantLock ) acquire/release manually; control than
more flexible than synchronized gives
synchronized
(timeouts, interruptible,
fairness policies)
Semaphore Controls how many Limiting concurrent
threads can access a access to a pool of N
resource concurrently resources
(not just 1)
Technique What it does When to use
Concurrent collections Data structures that are Shared
( ConcurrentHashMap , internally thread-safe, so lists/maps/queues
CopyOnWriteArrayList , BlockingQueue , you don't add your own across threads
etc.) locks
Exam tip: If asked to "list ways to synchronize state in Java," naming at least 4 of the
rows above (synchronized, volatile, atomic classes, explicit Locks, concurrent
collections) is a strong answer.
9. How to Identify Thread-Safe vs Not-Thread-Safe State
The test to apply: Can more than one thread read AND modify this piece of state at the
same time, where the final result depends on the exact timing/order of execution? If yes →
not thread-safe unless protected.
Classic sign of "not thread safe": a compound operation that looks like one line of code
but is secretly multiple steps:
java
counter++;
is really:
1. Read counter into a temporary value
2. Add 1
3. Write the new value back
If another thread sneaks in between steps 1 and 3, an update can be lost. This is a race
condition.
Demo: an UNSAFE counter (intentionally widened race window so the bug reliably
shows up)
java
public class RaceConditionDemo {
static int counter = 0; // shared mutable state
static void increment() {
int temp = counter; // step 1: read
temp = temp + 1; // step 2: compute
[Link](); // forces a thread switch here (widens the race
counter = temp; // step 3: write back
}
public static void main(String[] args) throws InterruptedException {
int numThreads = 4;
int incrementsPerThread = 500;
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) increment();
});
}
for (Thread t : threads) [Link]();
for (Thread t : threads) [Link]();
int expected = numThreads * incrementsPerThread;
[Link]("Expected counter: " + expected);
[Link]("Actual counter: " + counter);
[Link]("Lost updates: " + (expected - counter));
}
}
Output (ran 3 times — same result every time because of the forced [Link]() ):
Expected counter: 2000
Actual counter: 500
Lost updates: 1500
That's 1500 increments silently disappearing. This is exactly what a race condition looks
like — the symptom is "the final value is wrong/inconsistent," even though every individual
line of code is "correct."
Without the deliberate [Link]() , this same bug can still happen on a real
machine, but it might be rare and only show up occasionally — which is exactly why
race-condition bugs are so dangerous: they can pass all your tests and then fail
randomly in production.
10. How to Convert Not-Thread-Safe → Thread-Safe
There are 3 standard fixes for the counter above. Pick one depending on what's available:
Fix 1 — synchronized (mutual exclusion)
java
public class SynchronizedFix {
static int counter = 0;
static synchronized void increment() {
int temp = counter;
temp = temp + 1;
[Link]();
counter = temp;
}
public static void main(String[] args) throws InterruptedException {
int numThreads = 4, incrementsPerThread = 500;
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) increment();
});
}
for (Thread t : threads) [Link]();
for (Thread t : threads) [Link]();
[Link]("Expected counter: " + (numThreads * incrementsPerTh
[Link]("Actual counter: " + counter);
[Link]("Lost updates: " + ((numThreads * incrementsPerT
}
}
Output:
Expected counter: 2000
Actual counter: 2000
Lost updates: 0
Zero lost updates — synchronized forces every thread to take turns, even with the
[Link]() still trying to provoke a race.
Fix 2 — AtomicInteger (lock-free atomic operation)
java
import [Link];
public class AtomicFix {
static AtomicInteger counter = new AtomicInteger(0);
static void increment() {
[Link](); // the read-modify-write happens as ONE atom
}
public static void main(String[] args) throws InterruptedException {
int numThreads = 4, incrementsPerThread = 500;
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) increment();
});
}
for (Thread t : threads) [Link]();
for (Thread t : threads) [Link]();
[Link]("Expected counter: " + (numThreads * incrementsPerTh
[Link]("Actual counter: " + [Link]());
}
}
Output:
Expected counter: 2000
Actual counter: 2000
Fix 3 — explicit ReentrantLock (see Section 11 below for the full pattern)
Exam one-liner: To convert unsafe shared state to thread-safe, you must protect every
read-modify-write sequence so it executes as a single, uninterruptible unit — using
synchronized , an explicit Lock , or an atomic class.
11. Explicit Locks and Semaphores
Explicit Lock ( ReentrantLock )
synchronized is convenient but rigid — it locks/unlocks automatically around a block, and
you can't easily try-and-back-off or set a timeout. [Link] (most
commonly ReentrantLock ) gives you manual control: you call .lock() and .unlock()
yourself.
java
import [Link];
public class LockDemo {
static int counter = 0;
static ReentrantLock lock = new ReentrantLock();
static void increment() {
[Link](); // acquire the lock - other threads must wait here
try {
int temp = counter;
temp = temp + 1;
[Link]();
counter = temp;
} finally {
[Link](); // ALWAYS unlock in a finally block, even if an exce
}
}
public static void main(String[] args) throws InterruptedException {
int numThreads = 4, incrementsPerThread = 500;
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < incrementsPerThread; j++) increment();
});
}
for (Thread t : threads) [Link]();
for (Thread t : threads) [Link]();
[Link]("Expected: " + (numThreads * incrementsPerThread));
[Link]("Actual: " + counter);
}
}
Output:
Expected: 2000
Actual: 2000
Golden rule for ReentrantLock : always call unlock() inside a finally block. If you
forget and an exception is thrown between lock() and unlock() , the lock is never
released and every other thread waiting on it freezes forever.
Semaphore
A Semaphore is like a lock, but instead of allowing only 1 thread in at a time, it allows up to N
"permits." Think of a parking lot with a fixed number of slots — cars (threads) must wait if
all slots are taken.
java
import [Link];
public class SemaphoreDemo {
public static void main(String[] args) throws InterruptedException {
Semaphore parkingSlots = new Semaphore(2); // only 2 slots available
Runnable car = () -> {
String name = [Link]().getName();
try {
[Link](name + " wants to park... waiting for a slot
[Link](); // take a slot (blocks if
[Link](name + " PARKED. Slots left: " + parkingSlot
[Link](300);
[Link](name + " is LEAVING");
} catch (InterruptedException e) {
[Link]().interrupt();
} finally {
[Link](); // give the slot back
}
};
for (int i = 1; i <= 5; i++) {
new Thread(car, "Car-" + i).start();
}
}
}
Output:
Car-1 wants to park... waiting for a slot
Car-1 PARKED. Slots left: 1
Car-2 wants to park... waiting for a slot
Car-2 PARKED. Slots left: 0
Car-3 wants to park... waiting for a slot
Car-4 wants to park... waiting for a slot
Car-5 wants to park... waiting for a slot
Car-2 is LEAVING
Car-3 PARKED. Slots left: 0
Car-1 is LEAVING
Car-4 PARKED. Slots left: 0
Car-3 is LEAVING
Car-5 PARKED. Slots left: 0
Car-4 is LEAVING
Car-5 is LEAVING
Notice: with only 2 slots, only 2 cars are ever parked at once. Cars 3, 4, and 5 must wait until
a slot frees up.
Exam one-liner — Lock vs Semaphore: A lock (mutex) allows exactly one thread at a
time into a critical section. A semaphore generalizes this to allow up to N threads at a
time, using a counter of available "permits."
12. Blocking Queue
Plain English: A BlockingQueue is a queue that automatically pauses (blocks) a thread
when it tries to:
add an item to a full queue (waits until there's space), or
remove an item from an empty queue (waits until something arrives).
This makes it perfect for the classic producer–consumer pattern, and it removes the need
to manually write wait() / notify() synchronization code yourself — the queue handles all
of that internally.
java
import [Link];
import [Link];
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3); // capacity
Runnable producer = () -> {
try {
for (int i = 1; i <= 6; i++) {
[Link]("Producer making item " + i + " (queue s
[Link](i); // BLOCKS here automatically if the queue is
[Link](50);
}
} catch (InterruptedException e) {
[Link]().interrupt();
}
};
Runnable consumer = () -> {
try {
for (int i = 1; i <= 6; i++) {
[Link](200); // consumer is slower than producer
int item = [Link](); // BLOCKS here automatically if th
[Link]("Consumer took item " + item);
}
} catch (InterruptedException e) {
[Link]().interrupt();
}
};
Thread p = new Thread(producer, "Producer");
Thread c = new Thread(consumer, "Consumer");
[Link]();
[Link]();
[Link]();
[Link]();
[Link]("Done.");
}
}
Output:
Producer making item 1 (queue size before put: 0)
Producer making item 2 (queue size before put: 1)
Producer making item 3 (queue size before put: 2)
Producer making item 4 (queue size before put: 3)
Consumer took item 1
Producer making item 5 (queue size before put: 3)
Consumer took item 2
Producer making item 6 (queue size before put: 3)
Consumer took item 3
Consumer took item 4
Consumer took item 5
Consumer took item 6
Done.
Watch the pattern: the producer races ahead and fills the queue to capacity (3), then is
forced to block on put(4) until the slower consumer calls take() and frees a space. That's
blocking behavior in action — no manual locks were written by us at all.
13. Livelock and Deadlock
Both are liveness failures — the program doesn't crash, it just stops making progress. The
difference:
Deadlock Livelock
Threads' Completely stuck — blocked, doing Actively running, constantly changing state
state nothing
What Each thread holds a lock the other Threads keep "politely" backing off for each
happens needs, and waits forever other and retrying, but never actually succeed
Analogy Two people in a narrow hallway, Two people in a narrow hallway, both keep
each waiting for the other to move stepping aside at the same time, over and over,
first — frozen forever never managing to pass each other
Deadlock — the classic 2-lock example
This happens when two threads acquire two shared locks in opposite order:
java
public class DeadlockDemo {
static final Object lockA = new Object();
static final Object lockB = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lockA) {
[Link]("Thread-1 locked A, waiting for B...");
try { [Link](100); } catch (InterruptedException e) {}
synchronized (lockB) {
[Link]("Thread-1 got both locks");
}
}
}, "Thread-1");
Thread t2 = new Thread(() -> {
synchronized (lockB) { // <-- locks B first
[Link]("Thread-2 locked B, waiting for A...");
try { [Link](100); } catch (InterruptedException e) {}
synchronized (lockA) { // <-- then wants A
[Link]("Thread-2 got both locks");
}
}
}, "Thread-2");
[Link]();
[Link]();
[Link](3000); // wait at most 3 sec, don't hang forever
[Link](3000);
if ([Link]() || [Link]()) {
[Link]("DEADLOCK DETECTED: program is stuck, threads ne
} else {
[Link]("Both threads finished normally.");
}
}
}
Output (the program genuinely hangs — this is real captured output after waiting):
Thread-1 locked A, waiting for B...
Thread-2 locked B, waiting for A...
DEADLOCK DETECTED: program is stuck, threads never finished.
What happened: Thread-1 holds lockA and wants lockB . Thread-2 holds lockB and wants
lockA . Neither will ever let go of what it's holding, so both wait forever. This is called a
circular wait.
The 4 classic conditions required for deadlock (textbook list — good for exams!)
1. Mutual exclusion — resources can't be shared, only one thread can hold a lock at a
time.
2. Hold and wait — a thread holds one resource while waiting for another.
3. No preemption — a resource can't be forcibly taken away from a thread; it must be
released voluntarily.
4. Circular wait — a closed chain of threads, each waiting for a resource held by the next
one in the chain.
Break any one of these 4 conditions, and deadlock becomes impossible.
The fix — the part of the code that "addresses" deadlock
The simplest and most common fix: always acquire shared locks in the same, fixed order
everywhere in your program. This removes condition #4 (circular wait).
java
public class DeadlockFixDemo {
static final Object lockA = new Object();
static final Object lockB = new Object();
public static void main(String[] args) throws InterruptedException {
// FIX: both threads now lock A first, then B - the SAME order every ti
Thread t1 = new Thread(() -> {
synchronized (lockA) {
[Link]("Thread-1 locked A, waiting for B...");
try { [Link](100); } catch (InterruptedException e) {}
synchronized (lockB) {
[Link]("Thread-1 got both locks");
}
}
}, "Thread-1");
Thread t2 = new Thread(() -> {
synchronized (lockA) { // <-- changed from lockB to lockA: same o
[Link]("Thread-2 locked A, waiting for B...");
try { [Link](100); } catch (InterruptedException e) {}
synchronized (lockB) {
[Link]("Thread-2 got both locks");
}
}
}, "Thread-2");
[Link]();
[Link]();
[Link](3000);
[Link](3000);
if ([Link]() || [Link]()) {
[Link]("DEADLOCK DETECTED");
} else {
[Link]("Both threads finished normally - no deadlock!")
}
}
}
Output:
No more circular wait → no more deadlock. (Other valid fixes that exams sometimes
mention: using [Link]() with a timeout so a thread "gives up and retries" instead of
waiting forever, or using a single coarser lock for both resources.)
Quick Revision Cheat-Sheet (read this on Sunday night / Monday morning)
Question Short Answer
What is concurrency? Multiple tasks making progress over overlapping time periods
What is a thread? Smallest unit of execution; a single path of code that runs
independently
What is multithreading? Using multiple threads within one program/process
What is a thread pool? A reusable group of worker threads, avoiding the cost of creating a
new thread per task
Name 2 benefits of threads Better CPU use while waiting on I/O; improved responsiveness
Name 2 risks of threads Race conditions; deadlock
What is synchronization? Controlling how threads access shared data so they don't corrupt it
Name 3 ways to synchronize synchronized , explicit Lock , Atomic* classes (also:
volatile , concurrent collections)
How do you spot non-thread- Look for shared mutable state with a compound read-modify-write
safe code? that isn't protected
How do you fix it? Wrap the read-modify-write in synchronized , a Lock , or replace
it with an atomic class
Lock vs Semaphore? Lock = 1 thread at a time; Semaphore = up to N threads at a time
(permits)
Question Short Answer
What does a BlockingQueue Automatically blocks put() when full and take() when empty
do? — built for producer/consumer
Deadlock vs Livelock? Deadlock = threads frozen, waiting forever; Livelock = threads
actively running but never making progress
4 conditions for deadlock Mutual exclusion, hold-and-wait, no preemption, circular wait
Simplest deadlock fix Always acquire multiple locks in the same fixed order
Common Exam Traps
start() vs run() : calling run() directly does not create a new thread — it just runs
that code on the current thread. Only start() creates and schedules a real new
thread.
i++ is NOT atomic even though it looks like one operation — it's read + add + write.
volatile ≠ synchronized : volatile only guarantees visibility (everyone sees the
latest value), not atomicity of compound operations.
Forgetting [Link]() in a finally block is one of the most commonly tested
"what's wrong with this code" questions.
Deadlock requires all 4 conditions at once — if you can name which one a fix removes,
you'll usually get full marks on a "explain the fix" question.
Good luck on Monday — you've got this. 💪
Practice Questions — MCQ + Theory
Try the MCQs first without looking at the answer key (it's at the very bottom). Then
attempt the theory questions from memory, and only check the guidance notes after.
Part A — Multiple Choice Questions
Topic: Intro to Concurrent Programming
1. Which best defines concurrent programming? A. Writing a program that never has
bugs B. Structuring a program so multiple tasks can make progress over overlapping
time periods C. Writing a program in only one programming language D. Running a
program faster by removing all loops
2. What is the key difference between concurrency and parallelism? A. They are exactly
the same thing B. Concurrency requires multiple CPU cores; parallelism does not C.
Concurrency is about structuring multiple tasks; parallelism is about tasks literally
executing at the same instant on different cores D. Parallelism only applies to single-
threaded programs
3. Which of these is a real-world reason concurrent programming is used? A. To make
code shorter B. To better utilize multi-core CPUs and stay responsive while waiting on
I/O C. To remove the need for variables D. To avoid using functions
Topic: Threads
4. What is a thread? A. A separate running program with its own memory space B. The
smallest unit of execution within a process, with its own stack but sharing the
process's heap C. A file used to store program output D. A type of compiler error
5. In Java, what happens if you call run() directly instead of start() on a Thread
object? A. A new thread is created and the code runs concurrently B. The program
crashes immediately C. The code executes on the current thread — no new thread is
created D. Nothing happens; the method is ignored
6. Which interface is generally preferred over extending the Thread class, and why? A.
Comparable , because it sorts threads B. Runnable , because Java allows multiple
interface implementation but only single class inheritance C. Serializable , because
threads need to be saved to disk D. Iterable , because threads must be looped over
Topic: Multithreading
7. Multithreading refers to: A. Running the same thread twice B. The use/practice of
multiple threads operating within a single process C. Splitting a program into multiple
separate processes D. Writing a program without any threads
8. Which of the following is a valid concern introduced specifically by multithreading
(and not by single-threaded programs)? A. Syntax errors B. Race conditions between
threads sharing data C. Slower compilation time D. Larger source code files
Topic: Thread Pools
9. What is the main motivation for using a thread pool instead of creating a new Thread
for every task? A. Thread pools use less Java syntax B. Creating/destroying threads
repeatedly is expensive; pools reuse a fixed set of worker threads C. Thread pools
allow infinite threads to run at once D. Thread pools remove the need for the main
method
10. In Java, which interface/class is most commonly used to manage a thread pool? A.
Runnable B. ExecutorService (via Executors ) C. Thread D. Object
11. If you submit 6 tasks to [Link](3) , what should you expect?
A. 6 separate threads are created, one per task B. The program throws an exception
because there are too many tasks C. Only 3 worker threads exist; tasks are queued and
reused across those 3 threads D. Only the first 3 tasks will ever run
Topic: Benefits of Threads
12. Which of these is NOT typically considered a benefit of using threads? A. Better CPU
utilization during I/O waits B. Improved application responsiveness C. Guaranteed
elimination of all bugs in the program D. Resource sharing within the same process
Topic: Risks of Threads
13. What is a race condition? A. A competition between two programs to finish first B. A
situation where the outcome depends on the unpredictable timing/order of thread
execution on shared data C. An error that occurs only at compile time D. A type of
network protocol
14. What is "starvation" in the context of threads? A. A thread runs out of memory B. A
thread is repeatedly denied access to a resource because other threads keep getting
priority C. A thread finishes too quickly D. A thread that never starts because of a
missing import
15. Which of these is a genuine risk introduced by multithreading? A. Deadlock B. Faster
program compilation C. Smaller memory footprint, always D. Simpler debugging
compared to single-threaded code
Topic: Thread Synchronization
16. What is the main purpose of thread synchronization? A. To make all threads run at
exactly the same speed B. To control how multiple threads access shared data so they
don't corrupt it C. To convert threads into processes D. To remove the need for the
main method
17. In Java, what does every object have that the synchronized keyword uses? A. A serial
number B. A hidden internal lock called a monitor C. A unique thread name D. A built-
in thread pool
Topic: Different Ways to Synchronize State
18. Which keyword guarantees visibility of a variable's latest value across threads but
does NOT make compound operations (like i++ ) atomic? A. static B. final C.
volatile D. private
19. Which of these classes provides lock-free atomic operations such as
incrementAndGet() ? A. AtomicInteger B. ArrayList C. Scanner D. StringBuilder
20. Which of the following is a thread-safe collection that can be used instead of manually
adding locks around a HashMap ? A. ArrayList B. ConcurrentHashMap C. LinkedList D.
HashSet
Topic: Identifying Thread-Safe vs Not-Thread-Safe State
21. Why is counter++ considered NOT thread-safe when counter is shared across
threads? A. Because ++ is not valid Java syntax B. Because it is actually 3 separate
steps (read, add, write) that can be interrupted midway by another thread C. Because
integers cannot be incremented in Java D. Because ++ only works on String values
22. What is the best way to identify whether a piece of state is "not thread-safe"? A. Check
if the variable name contains an underscore B. Check if more than one thread can read
AND modify it concurrently, where the result depends on execution order/timing C.
Check if the variable is declared at the top of the file D. Check whether the program
compiles without errors
23. In the unsafe counter demo in this guide, what caused the "lost updates"? A. A typo in
the loop bound B. Two threads reading the same old value before either had written
back the incremented value C. The JVM running out of memory D. Using int instead
of String
Topic: Converting Unsafe State to Thread-Safe
24. Which of these is NOT a valid way to fix the unsafe counter example? A. Wrapping the
increment logic in a synchronized method B. Replacing int counter with
AtomicInteger C. Using a ReentrantLock around the read-modify-write D. Renaming
the variable counter to safeCounter
25. What is the general principle behind converting unsafe shared state into thread-safe
state? A. Make every variable final B. Ensure every read-modify-write sequence on
shared data executes as a single, uninterruptible unit C. Avoid using threads
altogether D. Always restart the JVM after each thread finishes
Topic: Explicit Lock and Semaphore
26. What is one advantage of ReentrantLock over the synchronized keyword? A. It
automatically unlocks itself even without a finally block B. It supports features like
try-locking with a timeout, which synchronized does not offer C. It is always faster in
every situation D. It removes the need for threads entirely
27. What MUST you always do after calling [Link]() on a ReentrantLock ? A. Call
[Link]() again immediately B. Call [Link]() , ideally inside a finally block
C. Restart the thread D. Nothing — it unlocks itself automatically
28. How does a Semaphore differ from a basic lock/mutex? A. A semaphore can allow up to
N threads to access a resource concurrently, not just 1 B. A semaphore can only be
used once per program C. A semaphore is identical to a lock in every way D. A
semaphore prevents all threads from running
29. In the parking lot semaphore example with new Semaphore(2) and 5 car threads, what
is true at any given moment? A. All 5 cars can be parked at once B. At most 2 cars can
be "parked" (holding a permit) at the same time C. No cars can ever park D. The
number of slots increases automatically over time
Topic: Blocking Queue
30. What happens when you call put() on a BlockingQueue that is already full? A. It
throws an exception immediately B. It silently does nothing C. The calling thread
blocks (waits) until space becomes available D. It deletes the oldest item automatically
31. What happens when you call take() on a BlockingQueue that is empty? A. It returns
null immediately B. The calling thread blocks (waits) until an item becomes
available C. It throws a compiler error D. It restarts the queue
32. Which classic concurrency pattern is BlockingQueue most associated with? A.
Singleton pattern B. Producer-consumer pattern C. Observer pattern D. Factory
pattern
Topic: Livelock and Deadlock
33. What is the key difference between deadlock and livelock? A. They are the same thing
with different names B. In deadlock threads are completely blocked/stuck; in livelock
threads keep actively changing state but never make real progress C. Deadlock only
happens with semaphores; livelock only happens with locks D. Livelock is always
worse than deadlock
34. Which of these is NOT one of the 4 classic necessary conditions for deadlock? A.
Mutual exclusion B. Hold and wait C. Garbage collection D. Circular wait
35. What is the simplest standard fix for the classic two-lock deadlock scenario (where
two threads lock two shared objects in opposite order)? A. Increase the JVM's
memory size B. Make both threads acquire the shared locks in the same, fixed order C.
Remove all synchronized keywords from the program D. Add more threads to the
program
36. Which technique can also help avoid deadlock by allowing a thread to "give up and
retry" instead of waiting forever for a lock? A. [Link](0) B. [Link]()
with a timeout C. Declaring the lock as static D. Removing the finally block
Mixed / Comparison Questions
37. Which pair is correctly matched? A. Lock — allows unlimited threads at once B.
Semaphore — allows exactly 1 thread at a time, always C. Lock — allows 1 thread at a
time; Semaphore — allows up to N threads at a time D. BlockingQueue — never blocks
under any condition
38. A program appears to "hang" with no output, and both suspect threads are alive but
never finish. This is most likely: A. A compiler error B. Deadlock C. A missing import
statement D. Successful thread completion
39. Which statement about volatile is TRUE? A. It makes compound operations like
counter++ fully atomic B. It guarantees other threads see the latest written value, but
does not provide atomicity for compound operations C. It is identical to synchronized
in every respect D. It can only be used on static variables
40. Why is debugging multithreaded programs often harder than debugging single-
threaded ones? A. Multithreaded programs never produce error messages B. Bugs
often depend on timing/thread scheduling, so they can be intermittent and hard to
reproduce C. Threads cannot be tested at all D. Multithreaded programs do not use
variables
Part B — Theory / Essay Questions
For each, key points you should be able to mention are listed underneath — practice writing
the full answer in your own words.
1. Define concurrent programming and explain why it is needed in modern computing.
Definition: multiple tasks make progress over overlapping time periods (not
necessarily simultaneously)
Reasons: multi-core CPU utilization, responsiveness during I/O waits, better
throughput
Mention concurrency vs parallelism distinction if relevant
2. Differentiate between concurrency and parallelism, with examples.
Concurrency = structure (interleaving tasks, can happen on 1 core)
Parallelism = execution (simultaneous on multiple cores)
Example: a single-core machine running 2 threads is concurrent but not parallel
3. What is a thread? Explain how it differs from a process.
Thread = smallest unit of execution, has own stack + program counter, shares
heap/memory with other threads in same process
Process = independent running program with its own memory space; communicating
between processes is more expensive than between threads
Extending Thread (simple, but uses up Java's single inheritance)
Implementing Runnable (more flexible — can implement other interfaces too;
separates task from thread mechanics)
Lambda expression implementing Runnable (concise, modern style)
5. Explain what multithreading is and list two benefits and two risks.
Definition: using multiple threads within a single process
Benefits: CPU utilization, responsiveness (pick any 2 from the Benefits section)
Risks: race conditions, deadlock, starvation, livelock (pick any 2)
6. What is a thread pool, and why is it preferred over manually creating threads for every
task?
Definition: fixed/reusable group of worker threads
Reasons: avoids overhead of repeated thread creation/destruction, controls the
maximum number of concurrently running threads, improves resource management
Mention ExecutorService / [Link]()
7. Explain thread synchronization and why it is necessary.
Definition: coordinating access to shared mutable state across threads
Necessary because uncoordinated access leads to race conditions / corrupted data
Mention Java's synchronized keyword and the monitor lock concept
8. List and briefly explain at least four different ways to synchronize state in Java.
synchronized methods/blocks, volatile , atomic classes ( AtomicInteger ), explicit
Lock ( ReentrantLock ), Semaphore , concurrent collections — explain each briefly
(refer to the table in Section 8)
9. With the aid of code, explain how to identify a piece of state as "not thread-safe," and
describe the symptom this produces.
Identify: shared mutable state + compound read-modify-write operation, accessed by
multiple threads without protection
Symptom: race condition → lost updates → final result is inconsistent/lower than
expected (cite the counter example: 1500 lost updates out of 2000 expected)
10. Describe two different techniques for converting a non-thread-safe counter into a
thread-safe one, including code sketches.
synchronized method wrapping the read-modify-write
[Link]()
(Optionally) ReentrantLock with lock()/unlock() in try/finally
11. Compare an explicit Lock (e.g. ReentrantLock ) with the synchronized keyword.
When would you choose one over the other?
synchronized : simpler, automatic unlock, tied to a block/method
ReentrantLock : manual lock/unlock, supports tryLock() with timeout, interruptible
locking, more flexible but riskier if you forget to unlock
Choose explicit lock when you need timeout/try-lock behavior or finer control
12. Explain how a Semaphore works and how it differs from a mutual-exclusion lock. Give
a real-world analogy.
Semaphore has a count of "permits"; threads acquire() before proceeding and
release() when done
Differs from a lock by allowing N concurrent holders instead of just 1
Analogy: parking lot with a fixed number of slots
13. What is a BlockingQueue ? Explain how it supports the producer-consumer pattern.
Queue that blocks put() when full and take() when empty
Producer thread adds items, consumer thread removes them; queue automatically
manages waiting, no manual wait() / notify() needed
Mention capacity-bounded queues like ArrayBlockingQueue
14. Distinguish between deadlock and livelock, and explain the four classic conditions
necessary for deadlock to occur.
Deadlock: threads completely blocked, waiting forever
Livelock: threads keep actively responding to each other but never progress
4 conditions: mutual exclusion, hold-and-wait, no preemption, circular wait
15. Using code or pseudocode, demonstrate a deadlock scenario and explain how you
would fix it.
Show two threads locking two objects in opposite order (lockA→lockB vs
lockB→lockA)
Fix: enforce a consistent lock-acquisition order across all threads (or use tryLock()
with timeout)
Explain which of the 4 conditions the fix removes (circular wait)
16. A junior developer writes if (counter < MAX) { counter++; } across multiple threads
without any synchronization. Identify the problem and propose a fix.
Problem: this is a "check-then-act" race condition — the check and the act are two
separate steps that can be interleaved by another thread, even though each line looks
atomic
Fix: wrap the entire check-and-increment in a single synchronized block (not just the
increment alone), or use AtomicInteger with a compare-and-set-style operation
Answer Key — MCQs
1-B, 2-C, 3-B, 4-B, 5-C, 6-B, 7-B, 8-B, 9-B, 10-B, 11-C, 12-C, 13-B, 14-B, 15-A, 16-B, 17-B, 18-C,
19-A, 20-B, 21-B, 22-B, 23-B, 24-D, 25-B, 26-B, 27-B, 28-A, 29-B, 30-C, 31-B, 32-B, 33-B, 34-C,
35-B, 36-B, 37-C, 38-B, 39-B, 40-B
Good luck on Monday — you've got this. 💪