Concurrency utilities in Java provide a robust framework for managing multiple threads and ensuring thread safety. These utilities are part of the java.util.concurrent package and offer various classes and interfaces to handle concurrent programming more efficiently.

Key Concepts

  1. Executor Framework: Manages a pool of worker threads, simplifying the execution of asynchronous tasks.
  2. Concurrent Collections: Thread-safe versions of standard collections like ConcurrentHashMap, CopyOnWriteArrayList, etc.
  3. Locks: More flexible and powerful alternatives to synchronized blocks, such as ReentrantLock.
  4. Atomic Variables: Provide lock-free thread-safe operations on single variables.
  5. Synchronizers: Utilities like CountDownLatch, CyclicBarrier, Semaphore, and Exchanger to manage thread coordination.

Executor Framework

The Executor framework provides a way to decouple task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc.

Example: Using ExecutorService

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

public class ExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            Runnable worker = new WorkerThread("" + i);
            executor.execute(worker);
        }
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("Finished all threads");
    }
}

class WorkerThread implements Runnable {
    private String command;

    public WorkerThread(String s) {
        this.command = s;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " Start. Command = " + command);
        processCommand();
        System.out.println(Thread.currentThread().getName() + " End.");
    }

    private void processCommand() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Explanation

  • ExecutorService: Manages a pool of threads.
  • Executors.newFixedThreadPool(3): Creates a thread pool with 3 threads.
  • executor.execute(worker): Submits tasks for execution.
  • executor.shutdown(): Initiates an orderly shutdown.
  • executor.isTerminated(): Checks if all tasks have completed.

Concurrent Collections

Concurrent collections are designed to be used in a multi-threaded environment without the need for external synchronization.

Example: Using ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);

        map.forEach((key, value) -> System.out.println(key + " = " + value));
    }
}

Explanation

  • ConcurrentHashMap: A thread-safe version of HashMap.
  • map.put(): Adds key-value pairs to the map.
  • map.forEach(): Iterates over the map entries.

Locks

Locks provide more extensive operations than synchronized blocks.

Example: Using ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final Lock lock = new ReentrantLock();

    public void performTask() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " is performing task");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLockExample example = new ReentrantLockExample();
        Runnable task = example::performTask;

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();
    }
}

Explanation

  • ReentrantLock: A reentrant mutual exclusion lock.
  • lock.lock(): Acquires the lock.
  • lock.unlock(): Releases the lock.

Atomic Variables

Atomic variables provide lock-free thread-safe operations on single variables.

Example: Using AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerExample {
    private AtomicInteger counter = new AtomicInteger(0);

    public void increment() {
        counter.incrementAndGet();
    }

    public int getCounter() {
        return counter.get();
    }

    public static void main(String[] args) {
        AtomicIntegerExample example = new AtomicIntegerExample();
        Runnable task = example::increment;

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Counter: " + example.getCounter());
    }
}

Explanation

  • AtomicInteger: Provides atomic operations on an int value.
  • counter.incrementAndGet(): Atomically increments by one.

Synchronizers

Synchronizers help manage the coordination between threads.

Example: Using CountDownLatch

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(3);

        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " is working");
            latch.countDown();
        };

        for (int i = 0; i < 3; i++) {
            new Thread(task).start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("All tasks are completed");
    }
}

Explanation

  • CountDownLatch: Allows one or more threads to wait until a set of operations being performed in other threads completes.
  • latch.countDown(): Decrements the count of the latch.
  • latch.await(): Causes the current thread to wait until the latch has counted down to zero.

Practical Exercise

Task

  1. Create a ConcurrentHashMap to store student names and their scores.
  2. Use an ExecutorService to simulate multiple threads adding scores to the map.
  3. Ensure thread safety and print the final map.

Solution

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

public class ConcurrencyExercise {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> studentScores = new ConcurrentHashMap<>();
        ExecutorService executor = Executors.newFixedThreadPool(3);

        Runnable task1 = () -> studentScores.put("Alice", 85);
        Runnable task2 = () -> studentScores.put("Bob", 90);
        Runnable task3 = () -> studentScores.put("Charlie", 95);

        executor.execute(task1);
        executor.execute(task2);
        executor.execute(task3);

        executor.shutdown();
        while (!executor.isTerminated()) {
        }

        studentScores.forEach((name, score) -> System.out.println(name + ": " + score));
    }
}

Explanation

  • ConcurrentHashMap: Ensures thread-safe operations.
  • ExecutorService: Manages the execution of tasks.
  • executor.execute(): Submits tasks for execution.

Conclusion

Concurrency utilities in Java provide powerful tools to manage multi-threaded applications efficiently. By using the Executor framework, concurrent collections, locks, atomic variables, and synchronizers, you can write robust and thread-safe code. Understanding and utilizing these utilities will significantly enhance your ability to handle concurrency in Java applications.

Java Programming Course

Module 1: Introduction to Java

Module 2: Control Flow

Module 3: Object-Oriented Programming

Module 4: Advanced Object-Oriented Programming

Module 5: Data Structures and Collections

Module 6: Exception Handling

Module 7: File I/O

Module 8: Multithreading and Concurrency

Module 9: Networking

Module 10: Advanced Topics

Module 11: Java Frameworks and Libraries

Module 12: Building Real-World Applications

© Copyright 2024. All rights reserved