Concurrency and Multithreading in Java: Advanced Techniques

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: Advanced Level

\n\n

Multithreading in Java is one of the most powerful and simultaneously complex topics. At the beginner level, we use synchronized and Thread, but building performant and scalable systems requires a deeper understanding. In this article, we will explore advanced concurrency mechanisms: thread pools, ForkJoinPool, asynchronous programming with CompletableFuture, fine-tuning locks, and memory management. The material is intended for developers already familiar with the basics of multithreading.

\n\n

1. Advanced Work with Thread Pools: ExecutorService and Configuration

\n\n

Using ExecutorService is the industry standard. However, the standard Executors.newFixedThreadPool(10) is not always suitable. Let's look at fine-tuning ThreadPoolExecutor.

\n\n
import java.util.concurrent.*;\n\npublic class CustomThreadPool {\n    public static void main(String[] args) {\n        int corePoolSize = 4;\n        int maxPoolSize = 8;\n        long keepAliveTime = 30;\n        TimeUnit unit = TimeUnit.SECONDS;\n        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);\n\n        ThreadPoolExecutor executor = new ThreadPoolExecutor(\n                corePoolSize,\n                maxPoolSize,\n                keepAliveTime,\n                unit,\n                workQueue,\n                new ThreadPoolExecutor.CallerRunsPolicy() // saturation policy\n        );\n\n        // Monitoring pool state\n        executor.submit(() -> {\n            System.out.println("Active threads: " + executor.getActiveCount());\n            System.out.println("Completed tasks: " + executor.getCompletedTaskCount());\n        });\n\n        executor.shutdown();\n    }\n}
\n\n

Key parameters:

\n
    \n
  • corePoolSize — the minimum number of threads kept alive even when idle.
  • \n
  • maxPoolSize — the maximum number of threads.
  • \n
  • keepAliveTime — the keep-alive time for excess threads.
  • \n
  • workQueue — the task queue. ArrayBlockingQueue with a bounded size prevents memory overflow.
  • \n
  • Saturation policy — what to do when the queue is full and all threads are busy. CallerRunsPolicy slows down the calling thread.
  • \n
\n\n

For long I/O tasks, it's better to increase corePoolSize; for CPU-bound tasks, base it on Runtime.getRuntime().availableProcessors().

\n\n

2. ForkJoinPool and Recursive Tasks

\n\n

ForkJoinPool is a specialized pool for tasks that can be divided into subtasks (divide and conquer). It efficiently uses work stealing.

\n\n

Example: recursively calculating the sum of array elements.

\n\n
import java.util.concurrent.RecursiveTask;\nimport java.util.concurrent.ForkJoinPool;\n\npublic class SumTask extends RecursiveTask<Long> {\n    private static final int THRESHOLD = 1000;\n    private final int[] array;\n    private final int start;\n    private final int end;\n\n    public SumTask(int[] array, int start, int end) {\n        this.array = array;\n        this.start = start;\n        this.end = end;\n    }\n\n    @Override\n    protected Long compute() {\n        if (end - start <= THRESHOLD) {\n            long sum = 0;\n            for (int i = start; i < end; i++) {\n                sum += array[i];\n            }\n            return sum;\n        }\n        int mid = (start + end) / 2;\n        SumTask leftTask = new SumTask(array, start, mid);\n        SumTask rightTask = new SumTask(array, mid, end);\n\n        leftTask.fork(); // asynchronous launch\n        long rightResult = rightTask.compute(); // compute                

Blogs

Book Recommendations