Concurrency and Multithreading in Java: A Complete Guide

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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

Blogs

Book Recommendations