Thread Basics & Synchronization
Before diving into complex frameworks, we must master the building blocks of concurrency: Threads and Shared Memory. In this chapter, we explore how Java manages execution and how to prevent data corruption when multiple threads access the same memory.
1. Processes vs. Threads
- Process: An independent program in execution (e.g., the JVM itself). It has its own isolated memory space.
- Thread: A lightweight unit of execution within a process. Threads share the process’s memory (Heap) but have their own execution stack.
[!NOTE] In Java, a “Platform Thread” maps 1:1 to an Operating System thread. Creating too many (e.g., 10,000+) can crash your application due to memory exhaustion.
2. Creating Threads in Java
The modern way to execute code in parallel is by implementing the Runnable interface.
public class ThreadDemo {
public static void main(String[] args) {
// 1. Define the task
Runnable task = () -> {
String name = Thread.currentThread().getName();
System.out.println("Hello from " + name);
};
// 2. Assign task to a thread
Thread t1 = new Thread(task, "Worker-1");
Thread t2 = new Thread(task, "Worker-2");
// 3. Start execution (asynchronous)
t1.start();
t2.start();
System.out.println("Main thread finished.");
}
}
3. The Problem: Shared State & Race Conditions
When multiple threads modify a shared variable without coordination, the result depends on the timing of their execution. This is a Race Condition.
Imagine two threads trying to increment a counter count++. This is not an atomic operation; it involves three steps:
- Read
countfrom memory. - Add 1 to the value.
- Write the new value back.
If two threads read 0 at the same time, they both write 1, and one increment is lost.
Interactive: Race Condition Simulator
Visualize how lack of synchronization leads to data loss.
4. The Solution: synchronized
To fix race conditions, we need Mutual Exclusion. The synchronized keyword ensures that only one thread can execute a block of code at a time for a given object instance.
Java Implementation
class Counter {
private int count = 0;
// Only one thread can enter this method at a time
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
You can also synchronize on a specific object (a “monitor”) rather than the whole method:
public void increment() {
synchronized(this) {
count++;
}
}
5. Visibility and volatile
Synchronization solves atomicity (all or nothing) and visibility. However, sometimes you only need Visibility—guaranteeing that changes made by one thread are immediately seen by others.
By default, threads may cache variables in CPU registers. The volatile keyword forces the JVM to read/write the variable directly from main memory.
[!WARNING]
volatiledoes NOT guarantee atomicity! It is useful for flags (e.g.,running = false) but not for counters (count++).
6. Java vs. Go: Concurrency Models
Java uses Shared Memory with Locks, while Go promotes Communicating Sequential Processes (CSP) (sharing memory by communicating).
Java: Threads & Locks
Heavy reliance on synchronized, Lock, and Atomic classes.
Go: Goroutines & Channels
Go uses lightweight “Goroutines” (similar to Virtual Threads) and “Channels” to pass data safely.
package main
import (
"fmt"
"sync"
)
// Go's "synchronized" equivalent is sync.Mutex
type SafeCounter struct {
mu sync.Mutex
count int
}
func (c *SafeCounter) Increment() {
c.mu.Lock() // Acquire lock
defer c.mu.Unlock() // Ensure unlock happens
c.count++
}
func main() {
counter := &SafeCounter{}
var wg sync.WaitGroup
// Spawn 1000 Goroutines
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait() // Wait for all to finish
fmt.Println("Final Count:", counter.count)
}
7. Summary
| Concept | Description |
|---|---|
| Race Condition | Undefined behavior when threads access shared state concurrently. |
| Critical Section | Code that accesses shared resources; must be protected. |
| Synchronized | Java keyword to enforce mutual exclusion (locks). |
| Volatile | Java keyword to enforce memory visibility (no caching). |
| Atomic | Classes like AtomicInteger that use CPU instructions for lock-free safety. |