Multithreading

Understand the concepts of multithreading and concurrency in Java. Learn how to create and manage threads to perform tasks concurrently.


Java Concurrency Utilities

Concurrency Utilities: An Overview

Concurrency in Java allows multiple parts of a program to execute seemingly simultaneously. This doesn't necessarily mean true parallel execution (especially on single-core systems), but it allows programs to be more responsive and efficient by utilizing available resources effectively. Java provides a rich set of concurrency utilities to manage and control the execution of concurrent tasks. These utilities help to avoid common concurrency problems like race conditions, deadlocks, and starvation.

Executors Framework

The Executors framework provides a high-level abstraction for managing threads. It decouples task submission from task execution, allowing you to create and manage thread pools easily. This is generally preferred over directly managing threads as it provides better resource management and simplifies code.

Executor Interface

The Executor interface is the base interface for all executor types. It provides a single method: execute(Runnable command), which executes the given runnable task at some point in the future.

 Executor executor = Executors.newSingleThreadExecutor();
            executor.execute(() -> {
                System.out.println("Task executed in a separate thread.");
            }); 

ExecutorService Interface

The ExecutorService interface extends Executor and provides more advanced features like submitting tasks that return a result (Future) and managing the lifecycle of the executor.

ThreadPools

Thread pools are a managed set of threads that are used to execute tasks concurrently. They reduce the overhead of creating and destroying threads for each task, improving performance.

FixedThreadPool

Creates a thread pool with a fixed number of threads. If all threads are busy, new tasks are queued until a thread becomes available.

 ExecutorService executorService = Executors.newFixedThreadPool(5); // Create a pool with 5 threads
            for (int i = 0; i < 10; i++) {
                final int taskNumber = i;
                executorService.submit(() -> {
                    System.out.println("Task " + taskNumber + " executed by " + Thread.currentThread().getName());
                });
            }
            executorService.shutdown(); // Important: Shuts down the executor after tasks are submitted 

CachedThreadPool

Creates a thread pool that creates new threads as needed, but will reuse previously created threads when they are available. Threads that have been idle for 60 seconds are terminated. Useful for short-lived, independent tasks.

 ExecutorService executorService = Executors.newCachedThreadPool();
            // Submit tasks...
            executorService.shutdown(); 

ScheduledThreadPoolExecutor

Allows scheduling tasks to run after a delay or periodically.

 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("Task executed periodically.");
            }, 1, 3, TimeUnit.SECONDS); //Initial delay 1 second, then every 3 seconds 

Remember to always shutdown() the ExecutorService after submitting tasks. This allows the program to terminate gracefully. The shutdown() method prevents new tasks from being submitted. Existing tasks continue to run. Use shutdownNow() to attempt to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.

Concurrent Collections

Standard Java collections are not thread-safe. Using them in concurrent environments can lead to data corruption and unpredictable behavior. The java.util.concurrent package provides concurrent versions of common collections, designed for safe access by multiple threads.

ConcurrentHashMap

A thread-safe hash table implementation that allows concurrent access and updates. It offers better performance than synchronizing access to a standard HashMap.

 ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
            map.put("key1", 1);
            map.compute("key1", (key, value) -> value + 1); // Atomic update 

ConcurrentLinkedQueue

A thread-safe, unbounded, FIFO (first-in-first-out) queue. It provides efficient concurrent access for adding and removing elements.

 ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
            queue.offer("item1");
            String item = queue.poll(); // Retrieves and removes the head of the queue 

CopyOnWriteArrayList

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array. This is useful when traversal operations vastly outnumber mutations.

 CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("item1");
            // Iterating is safe even if the list is modified concurrently
            for (String item : list) {
                System.out.println(item);
            } 

Other Concurrency Utilities

Locks

The java.util.concurrent.locks package provides more sophisticated locking mechanisms than the built-in synchronized keyword. Interfaces like Lock and implementations like ReentrantLock offer features such as timed waiting, fairness, and the ability to interrupt waiting threads.

Atomic Variables

The java.util.concurrent.atomic package provides classes that support lock-free, thread-safe programming on single variables. Classes like AtomicInteger, AtomicLong, and AtomicReference provide atomic operations for reading and updating their values without the need for explicit synchronization.

CountDownLatch

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

 CountDownLatch latch = new CountDownLatch(3); // Initialize with the number of tasks
            ExecutorService executor = Executors.newFixedThreadPool(3);

            for (int i = 0; i < 3; i++) {
                executor.submit(() -> {
                    try {
                        // Perform some task
                        System.out.println("Task completed by " + Thread.currentThread().getName());
                    } finally {
                        latch.countDown(); // Decrement the latch count when task is done
                    }
                });
            }

            try {
                latch.await(); // Wait until the latch count reaches zero
                System.out.println("All tasks completed!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                executor.shutdown();
            } 

CyclicBarrier

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point. Useful in parallel computing scenarios.