Concurrency Basics
IntermediateJava concurrency lets multiple threads execute simultaneously. Understanding threads, their lifecycle, and common pitfalls is foundational.
Overview
Java supports multi-threading natively via java.lang.Thread and java.lang.Runnable. Every Java application starts with the main thread; additional threads can be created to run tasks in parallel. Understanding the thread lifecycle (NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED), the causes of thread-safety issues (visibility, atomicity, ordering), and the Java Memory Model is essential before diving into higher-level concurrency utilities.
Creating and Starting Threads
Three main ways to create a thread: extend Thread, implement Runnable, or use a lambda. Prefer Runnable/lambda over extending Thread — it separates the task from the execution mechanism.
Thread.start() creates a new OS thread and runs run() on it. Calling run() directly executes on the current thread — a common mistake.
// 1. Extend Thread (not recommended for modern code)
class MyThread extends Thread {
@Override public void run() {
System.out.println("Running in: " + Thread.currentThread().getName());
}
}
new MyThread().start();
// 2. Implement Runnable (better — separates task from thread)
Runnable task = () -> System.out.println("Task in: "
+ Thread.currentThread().getName());
new Thread(task).start();
// 3. Thread factory with name (Java 19+)
Thread t = Thread.ofPlatform()
.name("worker-", 0)
.start(() -> System.out.println("Named thread"));
// Thread state inspection
Thread main = Thread.currentThread();
System.out.println(main.getName()); // main
System.out.println(main.getState()); // RUNNABLE
System.out.println(main.isDaemon()); // false
System.out.println(main.getPriority()); // 5Thread Lifecycle and Control
Thread states: NEW (created, not started), RUNNABLE (executing or ready), BLOCKED (waiting for monitor), WAITING (waiting indefinitely — Object.wait/join), TIMED_WAITING (waiting with timeout), TERMINATED (finished).
sleep() pauses the current thread for at least the specified duration. join() makes the calling thread wait until the target thread terminates. interrupt() sets the interrupt flag; threads must check it.
Thread worker = new Thread(() -> {
try {
System.out.println("Working...");
Thread.sleep(2000); // TIMED_WAITING
System.out.println("Done");
} catch (InterruptedException e) {
// Interrupt requested — clean up and exit
System.out.println("Interrupted!");
Thread.currentThread().interrupt(); // restore flag
}
});
worker.start();
System.out.println("Worker state: " + worker.getState()); // RUNNABLE
Thread.sleep(500);
worker.interrupt(); // wake it up early
worker.join(); // wait for worker to finish
System.out.println("Worker state: " + worker.getState()); // TERMINATEDRace Conditions and Thread Safety
A race condition occurs when the outcome depends on the unpredictable timing of thread scheduling. The classic example: two threads incrementing a shared counter without synchronization.
Three root causes of thread-safety problems: 1. Atomicity — read-modify-write operations are not atomic. 2. Visibility — changes by one thread may not be seen by others (CPU caches). 3. Ordering — the compiler and CPU may reorder instructions.
// Unsafe counter — race condition
public class UnsafeCounter {
private int count = 0;
public void increment() {
count++; // NOT atomic: read → add 1 → write (3 steps)
}
public int get() { return count; }
}
// Demonstrate the problem
UnsafeCounter counter = new UnsafeCounter();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
threads.add(new Thread(counter::increment));
}
threads.forEach(Thread::start);
for (Thread t : threads) t.join();
// Expected: 1000 — Actual: usually less due to lost updates
System.out.println("Count: " + counter.get());
// Solutions: synchronized, AtomicInteger, LongAdder
// (covered in next topics)Interactive Visualization
Key Points to Remember
- Prefer Runnable/lambda over extending Thread — separates the task from execution.
- Call start(), not run(), to launch a new thread.
- Thread states: NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED.
- interrupt() sets the interrupt flag; catch InterruptedException and restore the flag.
- Race conditions arise from atomicity, visibility, and ordering violations.
Practice Concurrency Basics in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat is the difference between start() and run() in Thread?
What are the possible states of a Java thread?
What is a race condition? Give an example.
What happens if you call interrupt() on a sleeping thread?
Why should you restore the interrupt flag in a catch(InterruptedException) block?
Ask Aria about Concurrency Basics
Your personal AI tutor — ask anything about this concept