This folder contains comprehensive examples of multithreading concepts in Java, designed specifically for beginners who want to understand how multithreading works.
Multithreading allows your program to run multiple tasks simultaneously (concurrently). Instead of doing one thing at a time, you can have multiple "threads" of execution working on different tasks at the same time.
Think of a restaurant kitchen:
- Single-threaded: One chef does everything (takes order, cooks, serves) - very slow!
- Multi-threaded: Multiple chefs work together (one takes orders, others cook, another serves) - much faster!
- Better Performance: While one thread waits for I/O, others can continue working
- Responsive Applications: UI stays responsive while background tasks run
- Parallel Processing: Utilize multiple CPU cores effectively
- Better Resource Utilization: Keep CPU busy instead of waiting
Comprehensive examples covering all major concepts:
-
Thread Creation (3 different ways)
- Extending Thread class (old way)
- Implementing Runnable interface (recommended)
- Using lambda expressions (modern way)
-
Thread Synchronization
- Synchronized blocks and methods
- ReentrantLock for advanced locking
- Why synchronization is needed
-
Thread Pools and Executors
- FixedThreadPool, CachedThreadPool
- Efficient thread management
- Future objects for getting results
-
Atomic Operations
- Thread-safe operations without locks
- AtomicInteger, AtomicLong, etc.
- Better performance than synchronization
-
Producer-Consumer Pattern
- Classic synchronization pattern
- Wait/notify mechanism
- Real-world applications
-
CompletableFuture
- Modern asynchronous programming
- Chaining operations
- Handling multiple futures
Simple demonstrations perfect for beginners:
-
Race Conditions
- What happens without synchronization
- Why results can be inconsistent
- How to reproduce the problem
-
Synchronization Solutions
- How synchronized methods fix race conditions
- Comparing safe vs unsafe code
- Understanding the difference
-
Thread Interruption
- Graceful thread termination
- Handling InterruptedException
- Best practices for cleanup
- Java 8 or higher installed
- Basic understanding of Java syntax
# Navigate to the multithreading folder
cd Multithreading
# Compile all Java files
javac *.java
# Run the comprehensive example (shows all concepts)
java MultithreadingExample
# Run the simple demo (perfect for beginners)
java ThreadDemo- MultithreadingExample: Shows all 6 examples with detailed explanations
- ThreadDemo: Demonstrates race conditions vs synchronized solutions
// Method 1: Extending Thread (not recommended)
Thread thread1 = new Thread() {
public void run() { /* your code */ }
};
// Method 2: Implementing Runnable (recommended)
Thread thread2 = new Thread(() -> {
/* your code */
});
// Method 3: Using ExecutorService (best practice)
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> { /* your code */ });// The Problem: Race Condition
public void incrementUnsafe() {
count++; // NOT thread-safe!
}
// The Solution: Synchronization
public synchronized void incrementSafe() {
count++; // Thread-safe!
}What happens:
- Thread A reads count = 5
- Thread B reads count = 5 (same value!)
- Thread A increments: 5 + 1 = 6
- Thread B increments: 5 + 1 = 6
- Both write 6 back - we lost one increment!
The fix:
- Use
synchronizedmethods or blocks - Only one thread can access the code at a time
- Prevents data corruption
// Instead of creating threads manually
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> { /* task 1 */ });
executor.submit(() -> { /* task 2 */ });
executor.shutdown(); // Always shutdown!// Instead of synchronized blocks
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // Thread-safe and fast!- Problem: Multiple threads accessing shared data without protection
- Solution: Use synchronization or atomic operations
- Test: Run your code multiple times - bugs can be intermittent!
- Problem: Two threads waiting for each other forever
- Solution: Always acquire locks in the same order
- Prevention: Use timeouts and avoid nested locks
// BAD
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Empty catch block - BAD!
}
// GOOD
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt status
return; // Exit gracefully
}// BAD - program might hang
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> { /* work */ });
// Forgot to shutdown!
// GOOD
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> { /* work */ });
executor.shutdown(); // Always shutdown!- Run it multiple times to see race conditions
- Understand why synchronization is needed
- See how synchronized methods fix the problem
- Run each example individually
- Read the detailed comments
- Try modifying the code to see what happens
- Create your own simple multithreaded programs
- Try different synchronization techniques
- Experiment with thread pools
- Learn about concurrent collections
- Study lock-free programming
- Explore reactive programming patterns
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// Your code here
} finally {
lock.unlock(); // Always unlock, even if exception occurs
}try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Restore interrupt status
return; // Exit gracefully
}// BAD - creates new thread for each task
new Thread(() -> { /* work */ }).start();
// GOOD - reuses threads efficiently
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> { /* work */ });// Instead of synchronized blocks for simple operations
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // Faster than synchronized methodsCompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);- Cause: Race conditions are timing-dependent
- Solution: Add more synchronization, test more thoroughly
- Cause: Deadlock or threads waiting forever
- Solution: Check for circular dependencies, add timeouts
- Cause: Shared data not properly protected
- Solution: Add synchronization or use atomic operations
- Cause: Too much synchronization or too many threads
- Solution: Profile your code, use thread pools, consider lock-free alternatives
- Oracle Java Tutorials: Concurrency in Java
- Java Concurrency in Practice: Excellent book by Brian Goetz
- Practice: Try implementing your own thread-safe data structures
- Tools: Use thread dumps and profilers to debug issues
Feel free to:
- Add more examples
- Improve documentation
- Fix bugs
- Suggest new concepts to cover
Remember: Multithreading can be tricky, but with practice and understanding, you'll master it! π