Semaphore and Exchanger in Java

Concurrency is a crucial aspect of any programming language, especially in today's multi-core and multi-threaded systems. Java, being a popular programming language, provides various mechanisms to handle concurrency effectively. In this article, we will explore two such mechanisms: Semaphore and Exchanger in Java.

Semaphore

Semaphore in Java is a synchronization aid that allows a fixed number of threads to access a shared resource concurrently. It maintains a set of permits, which determines the number of threads that can enter a critical section. When a thread wants to access the critical section, it must acquire a permit from the semaphore. Once inside the critical section, the thread can perform its operations. After finishing, it must release the permit, so other threads can acquire it and enter the critical section.

One can think of a semaphore as a bouncer at a club. The bouncer allows only a predefined number of people (threads) into the club (critical section) at a time. If the club is full, the bouncer tells incoming people to wait until some people leave and create space.

Java provides two types of semaphores: binary semaphore and counting semaphore.

Binary Semaphore: A binary semaphore has only two states - 0 and 1. It represents a lock that is either acquired or released. Binary semaphores are often used to protect critical sections where only one thread should enter at a time.

Counting Semaphore: A counting semaphore has a positive number of permits associated with it. It allows multiple threads to enter a critical section simultaneously up to the number of available permits.

Let's consider an example to understand the usage of Semaphore in Java:

import java.util.concurrent.Semaphore;

class SharedResource {
    private Semaphore semaphore;
    
    public SharedResource(int permits) {
        semaphore = new Semaphore(permits);
    }
    
    public void accessResource() {
        try {
            semaphore.acquire(); // acquire permit
            // perform operations on shared resource
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release(); // release permit
        }
    }
}

In the above example, we have a SharedResource class that has a Semaphore object associated with it. The Semaphore is instantiated with a specific number of permits. The accessResource() method demonstrates how to acquire and release a permit. Only a certain number of threads equal to the number of permits can access the shared resource concurrently.

Exchanger

Exchanger in Java is another synchronization mechanism that allows two threads to exchange data between them. It provides a rendezvous point where two threads can meet and swap objects. Each thread calls the exchange() method, providing its object as an argument. When both threads call this method, they exchange their objects, and each thread receives the object passed by the other thread.

Exchangers are useful when two threads need to synchronize with each other and exchange data between them. It simplifies the coordination between threads, as they can wait until both threads are ready to swap their data.

Here's an example to demonstrate the usage of Exchanger in Java:

import java.util.concurrent.Exchanger;

class DataExchange {
    private Exchanger<String> exchanger = new Exchanger<>();
    
    public void exchangeData(String data) {
        try {
            String exchangedData = exchanger.exchange(data); // exchange data with the other thread
            System.out.println(Thread.currentThread().getName() + " received: " + exchangedData);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

In the above example, we have a DataExchange class that has an Exchanger object associated with it. The exchangeData() method exchanges data with another thread using the exchange() method. When two threads call this method, they swap their data, and each thread receives the data provided by the other thread.

Conclusion

Concurrency is a challenging problem to solve, but Java provides powerful mechanisms like Semaphore and Exchanger to handle it effectively. Semaphores allow a fixed number of threads to access a shared resource concurrently, while Exchangers facilitate data exchange between two threads. By understanding and utilizing these mechanisms, developers can write more efficient and robust concurrent applications in Java.


noob to master © copyleft