[!NOTE] Historically, your Java program executed on a single "Main Thread". It processed line 1, then line 2. If line 2 was a 10-second database query, the application froze for 10 seconds before hitting line 3.
To build high-performance web servers (which handle 10,000 user requests simultaneously), we must execute code Concurrently across multiple CPU threads.
The Physical Thread Array
When a user runs a Java program, the JVM spawns an independent "Main Thread".
You can spawn your own background threads by:
- Creating a class that implements the
Runnableinterface. - Writing the heavy logic inside the
run()method. - Handing that object to a raw
Threadobject and executing.start().
// 1. Build the heavy background task logic
class HeavyTask implements Runnable {
public void run() {
System.out.println("Beginning huge 10-second data processing on Thread: " + Thread.currentThread().getName());
// ... intensive CPU math ...
}
}
public class Main {
public static void main(String[] args) {
System.out.println("Main Application Booting Up...");
// 2. Instantiate and hook it into a physical Thread!
Thread myWorkerThread = new Thread(new HeavyTask());
// 3. START IT! This instantly detaches vertically from the main thread!
myWorkerThread.start();
System.out.println("Main application continues immediately without waiting!");
}
}
Thread Pools & The ExecutorService
Raw threading is extremely dangerous. Spinning up millions of threads (e.g., one per user click) will crash the entire operating system, as threads inherently consume RAM.
Modern Java developers almost never use new Thread(). They use an ExecutorService (a Thread Pool).
You tell the system: "I'm giving you a pool of exactly 10 threads. Here are 5,000 tasks. Recycle those 10 threads to churn through the block."
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 1. Request a fixed squad of 5 heavily optimized worker threads
ExecutorService pool = Executors.newFixedThreadPool(5);
// 2. Submit 100 heavy jobs to the squad. They will balance the work perfectly!
for (int i = 0; i < 100; i++) {
pool.submit(new HeavyTask());
}
// 3. Order the pool to shutdown gracefully after the last job finishes.
pool.shutdown();
}
}
[!CAUTION] Race Conditions: If Thread A and Thread B try to update the exact same
int bankBalance = 100at the precise exact millisecond, they interfere and overwrite each other, causing money to vanish! You must use thesynchronizedkeyword orAtomicIntegervariables to lock memory addresses from being concurrently touched by competing threads.
Concurrency Is About Shared State
Running work on multiple threads is easy. Making shared data safe is the hard part. Most concurrency bugs happen when two threads read and write the same state without coordination.
Race Condition Example
class Counter {
private int value = 0;
void increment() {
value++;
}
}
value++ looks like one operation, but it is read, add, and write. Two threads can interleave those steps and lose updates.
Safer Counter
class Counter {
private final AtomicInteger value = new AtomicInteger();
void increment() {
value.incrementAndGet();
}
}
Practical Guidance
- Prefer immutable data when possible.
- Use thread pools instead of creating unlimited raw threads.
- Use concurrent collections for shared collection access.
- Keep synchronized sections small and easy to reason about.
Mini Practice
Create two tasks that increment a shared counter 1,000 times each. First observe the unsafe result with int, then fix it with AtomicInteger.
Practice Lab: Counter Race Condition
Observe why shared mutable state needs protection.
- Create a shared counter using plain
int. - Start two tasks that increment it 1,000 times each.
- Print the final value and observe that it may be incorrect.
- Replace the counter with
AtomicInteger. - Run again and compare the result.
Goal: Understand race conditions and one safe atomic alternative.
Revision Checkpoint
- Thread: Independent path of execution.
Runnable: Task that can run on a thread.ExecutorService: Manages reusable thread pools.- Race condition: Multiple threads access shared state unsafely.
- Atomic tools: Help protect simple shared updates.
Before the quiz: Explain why value++ is not safely atomic across threads.