Mutex Vs Semaphore
Mutex is a shared lock which only allows one resource to access at a time
Semaphore allows a number of resource to access at a time. When the resource finished accessing, it could release the lock for the next one to access
- Think of semaphore as like a restaurants where people waited to be seated. A number of people need to get out for another next number of people to get in
Mutex
To create a mutex we can use
import java.util.concurrent.locks.Lock;
Considering the code below
@ThreadSafe
public class MutexDemo {
private static final Lock mutex = new ReentrantLock();
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
IntStream.range(0, 5).forEach((i) -> executorService.submit(() -> {
try {
process();
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}));
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.HOURS);
System.out.println("Counter: " + counter);
}
public static void process() throws InterruptedException {
System.out.printf("Thread %s entered%n", Thread.currentThread().getName());
mutex.lock();
System.out.println(Thread.currentThread().getName() + " " + counter + " -> " + ++counter);
try {
Thread.sleep(5000);
} finally {
System.out.printf("Thread %s left%n", Thread.currentThread().getName());
mutex.unlock();
}
}
}
We use ReentrantLock
to make sure that the thread is able to re-enter the lock.
We use the try {} finally {}
block to make sure that the mutex
is still unlocked even if an exception happen
As a result, the program will have the following output:
Thread pool-1-thread-4 entered
Thread pool-1-thread-3 entered
Thread pool-1-thread-5 entered
Thread pool-1-thread-2 entered
Thread pool-1-thread-1 entered
pool-1-thread-4 0 -> 1
Thread pool-1-thread-4 left
pool-1-thread-3 1 -> 2
Thread pool-1-thread-3 left
pool-1-thread-5 2 -> 3
Thread pool-1-thread-5 left
pool-1-thread-2 3 -> 4
Thread pool-1-thread-2 left
pool-1-thread-1 4 -> 5
Thread pool-1-thread-1 left
Counter: 5
[!note]
In this case, since we only have 1 thread access a counter at a time, we don't need tosynchronized
theCounter
.
Semaphore
To create a semaphore, we use
import java.util.concurrent.Semaphore;
For example:
@ThreadSafe
public class SemaphoreDemo {
private static final Semaphore semaphore = new Semaphore(2);
private static volatile int counter = 0;
public static void main(String[] args) throws InterruptedException{
ExecutorService executorService = Executors.newFixedThreadPool(5);
IntStream.range(0, 5).forEach((i) -> {
executorService.submit(() -> {
try {
process();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
});
executorService.shutdown();
executorService.awaitTermination(1, TimeUnit.MINUTES); // Wait for tasks to complete
System.out.println("Counter: " + counter);
}
public static void process() throws InterruptedException {
System.out.println("Thread " + Thread.currentThread().getName() + " entered");
semaphore.acquire();
synchronized (SemaphoreDemo.class) {
System.out.println(Thread.currentThread().getName() + " acquired semaphore: " + counter + " -> " + ++counter);
}
try {
Thread.sleep(5000);
} finally {
System.out.println("Thread " + Thread.currentThread().getName() + " left");
semaphore.release();
}
}
}
In this example, we allow 2 threads to access the lock at the same time by setting Semaphore(2)
Similar to Mutex , we want to have the try {} finally {}
here to make sure that the Semaphore is eventually released.
The above will results the below:
Thread pool-1-thread-5 entered
Thread pool-1-thread-2 entered
Thread pool-1-thread-3 entered
Thread pool-1-thread-1 entered
Thread pool-1-thread-4 entered
pool-1-thread-5 acquired semaphore: 0 -> 1
pool-1-thread-2 acquired semaphore: 1 -> 2
Thread pool-1-thread-2 left
Thread pool-1-thread-5 left
pool-1-thread-3 acquired semaphore: 2 -> 3
pool-1-thread-1 acquired semaphore: 3 -> 4
Thread pool-1-thread-1 left
Thread pool-1-thread-3 left
pool-1-thread-4 acquired semaphore: 4 -> 5
Thread pool-1-thread-4 left
Counter: 5
As we can see, we have at most 2 threads executing in the same time. As a result, we need to synchronise the counter to make sure that parallely access is thread-safe.