Concurrency and Multithreading in Java: From Basics to Advanced Techniques
Modern applications require maximum performance. In Java, concurrency and multithreading are key tools for efficient use of multi-core processors. In this article, we will break down how to properly work with threads, synchronization, and modern concurrency utilities.
What is Multithreading in Java?
Multithreading is the ability of a program to execute multiple tasks simultaneously. In Java, each thread is a lightweight process with its own call stack. Main advantages:
- Increased performance through parallel execution
- More responsive user interface
- Efficient use of CPU resources
Creating Threads: Two Approaches
In Java, there are two main ways to create a thread:
1. Extending the Thread class
public class MyThread extends Thread { @Override public void run() { System.out.println("Thread is running: " + Thread.currentThread().getName()); }
public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Starting the thread }}2. Implementing the Runnable interface
public class Task implements Runnable { @Override public void run() { System.out.println("Task is running in thread: " + Thread.currentThread().getName()); }
public static void main(String[] args) { Thread thread = new Thread(new Task()); thread.start(); }}It is recommended to use Runnable because it does not limit the ability to inherit from other classes.
Thread Synchronization: Avoiding Race Conditions
When multiple threads access shared data, synchronization problems arise. Let's look at a classic example — a counter:
public class Counter { private int count = 0;
public synchronized void increment() { count++; }
public synchronized int getCount() { return count; }}The synchronized keyword guarantees that only one thread can execute the method at a time. An alternative is using Lock from the java.util.concurrent.locks package.
Deadlock: How to Avoid Mutual Blocking
Deadlock occurs when two threads are waiting for each other's resources to be released. Example:
public class DeadlockExample { private final Object lock1 = new Object(); private final Object lock2 = new Object();
public void method1() { synchronized (lock1) { synchronized (lock2) { // Critical section } } }
public void method2() { synchronized (lock2) { synchronized (lock1) { // Critical section } } }}To avoid deadlock, always acquire locks in the same order or use tryLock() with a timeout.
ExecutorService: Managing a Thread Pool
Creating threads manually is inefficient. Java provides ExecutorService for managing a thread pool:
import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;
public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) { executor.submit(() -> { System.out.println("Executing in: " + Thread.currentThread().getName()); }); }
executor.shutdown(); // Shutdown after completing all tasks }}Main t