Appearance
Semaphore
In the previous discussions, we covered various lock implementations. The primary purpose of a lock is to protect a limited resource, ensuring that at any given time only one thread can access it (ReentrantLock
), or only one thread can write to it (ReadWriteLock
).
There is another scenario where a limited resource must ensure that no more than N threads can access it simultaneously. For example, limiting the number of database connections to 100 at any given time or allowing a maximum of 10 users to download simultaneously.
Implementing such a quantity restriction using an array of locks would be cumbersome. Instead, Semaphore
is ideal for handling this situation. It allows setting a limit on the number of concurrent accesses. For instance, to permit a maximum of 3 threads to access simultaneously:
java
public class AccessLimitControl {
// Allow up to 3 threads to acquire a permit at any given time:
final Semaphore semaphore = new Semaphore(3);
public String access() throws Exception {
// If the number of permits is exceeded, other threads will wait here:
semaphore.acquire();
try {
// Perform some task:
return UUID.randomUUID().toString();
} finally {
semaphore.release();
}
}
}
With Semaphore
, we first call acquire()
to obtain a permit and then use a try ... finally
block to ensure that the permit is released in the finally
section.
Calling acquire()
might cause the thread to wait until a permit becomes available. The tryAcquire()
method allows specifying a maximum wait time:
java
if (semaphore.tryAcquire(3, TimeUnit.SECONDS)) {
// Acquired a permit within 3 seconds:
try {
// Perform some task:
} finally {
semaphore.release();
}
}
A Semaphore
essentially acts as a counter that tracks the number of available permits, limiting the number of simultaneous accesses.
Summary
To limit access to a constrained resource, use a Semaphore
to ensure that no more than N threads can access the resource at the same time.