Concurrency In JAVA

1. Introduction to ExecutorService

The ExecutorService in Java provides a framework to manage thread pools and execute tasks concurrently. It allows you to create and manage a single thread or a pool of threads to handle tasks efficiently.

Basic Example:

ExecutorService executor = Executors.newFixedThreadPool(3);
Runnable task = () -> System.out.println("Task executed by " + Thread.currentThread().getName());
executor.submit(task);
executor.shutdown();

2. Runnable vs Callable

Both Runnable and Callable interfaces can be submitted to an ExecutorService. However, Callable returns a result and can throw checked exceptions, while Runnable does not.

Runnable Example:

Runnable task = () -> System.out.println("Runnable executed");
executor.submit(task);

Callable Example:

Callable<Integer> task = () -> {
    Thread.sleep(1000);
    return 42;
};
Future<Integer> future = executor.submit(task);
System.out.println("Result: " + future.get());

3. ScheduledExecutorService

Use ScheduledExecutorService to schedule tasks to run periodically at a fixed rate or with a fixed delay.

Example:

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> System.out.println("Scheduled Task"), 0, 2, TimeUnit.SECONDS);

4. Synchronization and Monitors

Synchronization ensures that only one thread can access a particular section of code at a time. Monitors are implemented using synchronized blocks or synchronized methods.

Synchronized Block Example:

Object lock = new Object();
Thread t1 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("Thread 1 is executing");
    }
});

Locks

Locks are mechanisms that control access to shared resources by multiple threads, ensuring mutual exclusion. They prevent issues like race conditions and data inconsistency.

Types of Locks

  • Implicit Locks: Built-in to Java and used with synchronized blocks or methods.
  • Explicit Locks: Provided by the java.util.concurrent.locks package, offering advanced features like try-lock and fairness policies.

Example of Implicit Lock

public class SharedResource {
    private int counter = 0;

    // Synchronized method: Locks on the current instance of SharedResource
    public synchronized void increment() {
        counter++;
        System.out.println(Thread.currentThread().getName() + " incremented counter to: " + counter);
    }

    // Synchronized block: Locks on a specific object (this instance)
    public void decrement() {
        synchronized (this) {
            counter--;
            System.out.println(Thread.currentThread().getName() + " decremented counter to: " + counter);
        }
    }
}

Example of Explicit Lock

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

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

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

5. Atomic Classes

Atomic classes ensure that operations occur without interference by another thread. These are part of the Concurrency API.

Example of AtomicInteger:

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();
System.out.println("Atomic value: " + count.get());

6. Concurrent Collection Classes

The Concurrency API includes thread-safe collections like ConcurrentHashMap and CopyOnWriteArrayList.

Example:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
System.out.println("Value: " + map.get("key"));

7. Parallel Streams

Parallel streams allow concurrent execution of stream operations. However, caution must be taken as the order of operations may not be predictable.

Example:

List<Integer> list = Arrays.asList(1, 2, 3, 4);
list.parallelStream().forEach(System.out::println);

8. CyclicBarrier and Fork/Join Framework

CyclicBarrier is used to synchronize threads at a common point, while the fork/join framework divides tasks recursively.

CyclicBarrier Example:

CyclicBarrier barrier = new CyclicBarrier(2);
new Thread(() -> {
    System.out.println("Thread 1 waiting");
    barrier.await();
}).start();

9. Common Threading Problems

  • Deadlock: Threads are blocked forever, waiting for each other's resources.
  • Starvation: Threads are perpetually denied access to resources.
  • Livelock: Threads are active but unable to make progress.
  • Race Condition: Multiple threads access a shared resource without proper synchronization.

Example of Race Condition:

int count = 0;
Runnable increment = () -> count++;
executor.submit(increment);
executor.submit(increment);

In this example, without synchronization, the value of count may not be incremented correctly.

Conclusion

Java provides powerful tools for concurrent programming through the ExecutorService, Callable, Runnable, synchronization mechanisms, atomic classes, and concurrent collections. By understanding these concepts, developers can build efficient, thread-safe applications.

Post a Comment

0 Comments