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.
0 Comments