Concurrency and multithreading are fundamental concepts for any backend or system-level developer. They play a critical role in building high-performance, scalable, and robust applications. This detailed, step-by-step guide will help you master these concepts with practical examples and best practices.
1. Introduction to Concurrency and Multithreading
What is Concurrency?
Concurrency is the ability of a program to perform multiple tasks simultaneously. It allows different parts of a program to run independently, enhancing performance and responsiveness.
What is Multithreading?
Multithreading is a specific form of concurrency where multiple threads run in parallel within a single program. Each thread represents a separate path of execution.
Concept | Definition |
---|---|
Process | An independent program with its memory space. |
Thread | A lightweight sub-process sharing the same memory space. |
Concurrency | Managing multiple tasks simultaneously. |
Parallelism | Actual execution of multiple tasks at the same time. |
2. Creating Threads in Java
2.1. Extending Thread
Class
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
}
}
2.2. Implementing Runnable
Interface
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable thread is running...");
}
}
public class RunnableExample {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
}
}
Best Practice: Prefer Runnable
over Thread
because it promotes better design by decoupling task definition from thread execution.
3. Thread Lifecycle
State | Description |
---|---|
New | Thread is created but not started. |
Runnable | Thread is ready to run, waiting for CPU time. |
Blocked | Waiting to acquire a lock. |
Waiting | Indefinitely waiting for another thread's signal. |
Timed Waiting | Waiting for a specified time. |
Terminated | Thread has finished execution. |
4. Concurrency Problems
Common Issues
- Race Condition – Two or more threads accessing shared data leading to inconsistent results.
- Deadlock – Two or more threads are waiting for each other indefinitely.
- Livelock – Threads keep changing states but make no progress.
- Starvation – A thread is denied CPU access due to other high-priority threads.
5. Synchronization in Java
Synchronization is the process of controlling the access of multiple threads to shared resources.
5.1. synchronized
Keyword
Synchronizes a method or block to allow only one thread to access it at a time.
5.1.1. Synchronized Method
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
5.1.2. Synchronized Block
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
5.2. Lock Interface
Provides more control compared to synchronized
.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
Approach | Flexibility | Performance |
---|---|---|
synchronized |
Less | Generally Fast |
Lock |
High | Slightly Slower |
6. Advanced Concurrency Tools
6.1. volatile
Keyword
Ensures visibility of changes to variables across threads.
private volatile boolean running = true;
6.2. Atomic
Variables
Provides thread-safe operations without locking.
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
6.3. Executors Framework
Creates and manages thread pools.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(() -> System.out.println("Task executed"));
executor.shutdown();
Executor Type | Description |
---|---|
newFixedThreadPool(n) |
Pool with a fixed number of threads. |
newCachedThreadPool() |
Expands as needed, reuses idle threads. |
newSingleThreadExecutor() |
Single-thread executor. |
7. Thread Communication
7.1. wait()
and notify()
Used for inter-thread communication within synchronized context.
synchronized (lock) {
lock.wait(); // Releases the lock and waits
lock.notify(); // Wakes up a waiting thread
}
Method | Description |
---|---|
wait() |
Makes the current thread wait. |
notify() |
Wakes up a single waiting thread. |
notifyAll() |
Wakes up all waiting threads. |
8. Deadlock Example
class A {
synchronized void foo(B b) {
System.out.println("Thread1 trying to call B's last()");
b.last();
}
synchronized void last() {
System.out.println("Inside A's last()");
}
}
class B {
synchronized void bar(A a) {
System.out.println("Thread2 trying to call A's last()");
a.last();
}
synchronized void last() {
System.out.println("Inside B's last()");
}
}
Avoid deadlocks using lock ordering or tryLock() from ReentrantLock
.
9. Best Practices for Multithreading
- Use
ExecutorService
instead of manually creating threads. - Minimize shared data between threads.
- Use
synchronized
,Locks
,volatile
, andAtomic
wisely. - Avoid unnecessary synchronization as it can degrade performance.
- Identify thread-safety requirements early in development.
- Test for concurrency issues using stress tests and tools like FindBugs.
10. Common Interview Questions
- Difference between
synchronized
andLock
? - What is
volatile
and when to use it? - How to avoid deadlocks?
- What is a thread-safe class in Java?
- Explain thread pool benefits.
Conclusion
Concurrency, multithreading, and synchronization are crucial for building robust and high-performance applications. Understanding these concepts in depth will help you write efficient, thread-safe, and scalable software. Practice writing concurrent programs, experiment with different synchronization techniques, and always consider thread-safety when dealing with shared resources.
Next Steps:
- Implement thread-safe classes using
synchronized
andLock
. - Use
ExecutorService
in real projects. - Explore advanced concurrent utilities like
CountDownLatch
,Semaphore
, andCyclicBarrier
.
Happy Coding!
Would you like diagrams or code snippets with more real-world scenarios for your blog?