Concurrency and Multithreading in Java: Advanced Level
\n\nMultithreading 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.
1. Advanced Work with Thread Pools: ExecutorService and Configuration
\n\nUsing ExecutorService is the industry standard. However, the standard Executors.newFixedThreadPool(10) is not always suitable. Let's look at fine-tuning ThreadPoolExecutor.
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\nKey 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.ArrayBlockingQueuewith a bounded size prevents memory overflow. \n - Saturation policy — what to do when the queue is full and all threads are busy.
CallerRunsPolicyslows down the calling thread. \n
For long I/O tasks, it's better to increase corePoolSize; for CPU-bound tasks, base it on Runtime.getRuntime().availableProcessors().
2. ForkJoinPool and Recursive Tasks
\n\nForkJoinPool is a specialized pool for tasks that can be divided into subtasks (divide and conquer). It efficiently uses work stealing.
Example: recursively calculating the sum of array elements.
\n\nimport 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