Topic 11 of 13 · Lectures 19–21
Multithreaded Programming
Running several tasks at once with threads: creating and starting them, the thread life cycle, race conditions and synchronization, and coordinating threads with wait and notify.
← → move between slides · F full screen · M slide list
Before we start
Why this matters
Your music keeps playing while you scroll, download and type. How can one program do several things at the same time?
Each activity runs in its own thread: an independent path of execution inside the same program. Threads make programs responsive and let them use every CPU core.
But threads that share data can trip over each other. You will create threads, then break a counter on purpose, and fix it with synchronization.
Outline · Lectures 19–21
Topics in this chapter
Select a topic to jump to the slide that explains it.
Concept 1 of 6
Processes and threads
Concept 1 of 6
Processes and threads (cont.)
- A process is a running program with its own memory. A thread is a path of execution inside a process.
- Threads share the heap (objects, static fields), so they communicate cheaply — and can interfere.
- Every Java program starts with the main thread; the JVM also runs background threads such as the garbage collector.
- On a multi-core CPU threads can truly run in parallel; on one core they take turns (time-slicing).
Concept 2 of 6
The thread life cycle
The thread life cycle
| State | Meaning |
|---|---|
| New | Created with new Thread(...), not started yet. |
| Runnable | After start(): ready, waiting for the CPU. |
| Running | The scheduler gave it the CPU; run() is executing. |
| Blocked | Idle after sleep(), wait() or suspend(); back to Runnable on notify(), resume() or time-out. |
| Dead | run() finished, or the thread was stopped. |
Code example
Creating threads
class Worker extends Thread { // way 1
Worker(String name) { super(name); }
public void run() {
for (int i = 1; i <= 3; i++)
System.out.println(getName() + " step " + i);
}
}
class Task implements Runnable { // way 2 (preferred)
public void run() {
System.out.println("Task in " + Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
Thread w = new Worker("W");
Thread t = new Thread(new Task(), "T");
Thread l = new Thread(() -> System.out.println("Lambda"), "L"); // way 3
w.start(); t.start(); l.start();
Code example
Creating threads (cont.)
w.join(); t.join(); l.join();
System.out.println("main ends");
}W step 1 Task in T W step 2 Lambda W step 3 main ends
- Put the thread’s work in
run(), but callstart()— start creates the new thread, which then calls run. - Implementing
Runnablekeeps your class free to extend something else. - The order of lines from different threads can change on every run.
Step through the code
Trace: two threads, two stacks
Every thread has its own call stack. Watch the states change from NEW to RUNNABLE to TERMINATED, and main WAIT in join().
Step through the code
Trace: two threads, two stacks (cont.)
Interactive
Two threads, many outputs
Code example
sleep, join and other Thread methods
Thread timer = new Thread(() -> {
for (int s = 3; s >= 1; s--) {
System.out.println(s + "...");
try { Thread.sleep(1000); } // pause 1 s
catch (InterruptedException e) { return; }
}
System.out.println("Go!");
});
timer.start();
System.out.println("Waiting for the countdown");
timer.join(); // wait until timer ends
System.out.println("Race started, alive? " + timer.isAlive());Waiting for the countdown 3... 2... 1... Go! Race started, alive? false
Code example
sleep, join and other Thread methods (cont.)
| Method | Purpose |
|---|---|
Thread.sleep(ms) | Pause the current thread (TIMED_WAITING). |
t.join() | Wait for thread t to finish. |
t.isAlive(), getName(), setPriority(1–10) | Query or tune a thread (priority is only a hint). |
t.interrupt() | Ask a sleeping or waiting thread to stop: it gets InterruptedException. |
Concept 3 of 6
Race conditions
class Counter {
private int count = 0;
void increment() { count++; } // NOT atomic
int get() { return count; }
}
Counter c = new Counter();
Runnable job = () -> { for (int i = 0; i < 100_000; i++) c.increment(); };
Thread a = new Thread(job), b = new Thread(job);
a.start(); b.start(); a.join(); b.join();
System.out.println(c.get()); // expected 200000137462
count++is three steps: read count, add 1, write it back.- If both threads read the same old value, one increment is lost.
- A bug whose result depends on timing is a race condition. It may pass a hundred tests and fail in the exam demo.
Step through the code
Trace: how count++ loses an update
count++ is read, add, write. Compare the two schedules: in the interleaved one, both threads read 0.
Step through the code
Trace: how count++ loses an update (cont.)
Interactive
Race-condition lab
Concept 4 of 6
Synchronization
class Counter {
private int count = 0;
synchronized void increment() { count++; } // method lock on this
synchronized int get() { return count; }
}
class Bank {
private final Object lock = new Object();
private double balance;
void deposit(double amt) {
synchronized (lock) { // block lock
balance += amt;
}
}
}- Every object has a monitor lock. Only one thread at a time can hold it.
- A
synchronizedmethod locksthis(a static synchronized method locks the class). - Other threads trying to enter become BLOCKED until the lock is released.
- Keep synchronized sections short; for simple counters
AtomicIntegeris an alternative.
Step through the code
Trace: a lock makes B wait
With synchronized, B is BLOCKED while A holds the lock, so it can never read a stale value.
Step through the code
Trace: a lock makes B wait (cont.)
Concept 5 of 6
Interthread communication: wait and notify
class Buffer {
private final Queue<Integer> q = new LinkedList<>();
private final int CAP = 3;
synchronized void put(int x) throws InterruptedException {
while (q.size() == CAP) wait(); // release lock and sleep
q.add(x);
notifyAll(); // wake waiting takers
}
synchronized int take() throws InterruptedException {
while (q.isEmpty()) wait();
int x = q.remove();
Concept 5 of 6
Interthread communication: wait and notify (cont.)
notifyAll(); // wake waiting putters
return x;
}
}wait()releases the lock and waits until another thread callsnotify()/notifyAll()on the same object.- All three may only be called while holding that object’s lock (inside synchronized).
- Always wait in a
whileloop: re-check the condition after waking up. - This is the classic producer–consumer pattern.
Interactive
Producer–consumer
Concept 6 of 6
Deadlock
Deadlock
// Thread 1 // Thread 2
synchronized (a) { synchronized (b) {
synchronized (b) { ... } synchronized (a) { ... }
} }- A deadlock happens when threads wait for each other in a cycle; none can continue.
- Prevent it by always taking locks in the same order, holding locks briefly, or using timeouts (
tryLock).
Worked example 1 of 3
Worked example 1
Thread t = new Thread(task); t.run();Show the solution
t.start() to create a new thread.Worked example 2 of 3
Worked example 2
synchronized void inc() 1000 times on the same object. What is the final count? What if each thread uses its own object?Show the solution
Worked example 3 of 3
Worked example 3
wait() be called in a while loop rather than an if?Show the solution
Practice
Practice (1 of 2)
Try each one on paper first, then check your answer.
Show the answer
Thread.sleep(500)?Show the answer
Show the answer
Thread[] ts = new Thread[3];
for (int i = 0; i < 3; i++) {
ts[i] = new Thread(() -> System.out.println(Thread.currentThread().getName()), "T" + i);
ts[i].start();
}
for (Thread t : ts) t.join();
System.out.println("done");Practice
Practice (2 of 2)
Try each one on paper first, then check your answer.
count++ not thread-safe?Show the answer
sleep() and wait()?Show the answer
Show the answer
Check yourself
Quiz
Summary
Summary
- A thread is an independent path of execution; threads share the heap but have their own stacks.
- Create threads by extending Thread or implementing Runnable; always call start(), not run().
- States: NEW → RUNNABLE ↔ BLOCKED / WAITING / TIMED_WAITING → TERMINATED.
- Unsynchronized shared updates cause race conditions; synchronized methods/blocks give one thread the lock at a time.
- wait/notifyAll coordinate threads (producer–consumer); consistent lock ordering avoids deadlock.