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 Theory to Practice



In the modern world of software, application performance directly depends on the ability to efficiently use processor resources. Concurrency and multithreading in Java are key concepts that allow you to execute multiple tasks simultaneously, speed up data processing, and create responsive user interfaces. In this article, we will break down the basic mechanisms of multithreading in Java, learn how to create and manage threads, and also consider best practices for avoiding common mistakes.



What is a thread and how to create one in Java?



A thread is a lightweight process that executes within a single application. Each thread has its own call stack and can operate independently of other threads. In Java, there are two main ways to create a thread.



Method 1: Inheriting from the Thread class

public class MyThread extends Thread {    @Override    public void run() {        System.out.println("Thread " + getName() + " started!");    }

public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); // Starting the thread }}


Method 2: Implementing the Runnable interface

public class MyRunnable implements Runnable {    @Override    public void run() {        System.out.println("Thread is running via Runnable");    }

public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.start(); }}


It is recommended to use the second method, as it does not require inheritance and allows implementing multiple interfaces simultaneously.



Thread synchronization: avoiding race conditions



When multiple threads access shared data simultaneously, a race condition occurs. To avoid this, Java provides synchronization mechanisms.



The synchronized keyword

Blocks access to a method or block of code for other threads until the current thread finishes execution:

public class Counter {    private int count = 0;

public synchronized void increment() { count++; }

public synchronized int getCount() { return count; }}


Locks using Lock

A more flexible mechanism than synchronized. Allows setting timeouts and attempting to acquire a lock without blocking:

import java.util.concurrent.locks.ReentrantLock;

public class SafeCounter { private final ReentrantLock lock = new ReentrantLock(); private int count = 0;

public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // Always release the lock } }}


Thread pool and ExecutorService



Creating a new thread for each task is an expensive operation. To manage threads efficiently, use ExecutorService from the java.util.concurrent package.



import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;

public class ThreadPoolExample { public static void main(String[] args) { // Create a pool of 4 threads ExecutorService executor = Executors.newFixedThreadPool(4);

for (int i = 0; i < 10; i++) { final int taskId = i; executor.submit(() -> { System.out.println("Task " + taskId + " is executing in " + Thread.currentThread().getName()); }); }

executor.shutdown(); // Shut down the pool after all tasks are completed }}


Main types of pools:

  • newFixedThreadPool(n) — fixed number of threads

Blogs

Book Recommendations