Understanding Java Semaphore for Rate Limiting and Concurrency Control
This article explains the purpose and usage of Java's Semaphore class, demonstrates how it can be applied for rate limiting with real‑world analogies, provides a complete parking‑lot example with source code, and discusses fairness modes and additional Semaphore APIs.
Basic Information
Name: Semaphore
Chinese name: (计数)信号量
Introduced in: JDK 1.5
Origin: java.util.concurrent (JUC) package
Purpose: A Java synchronizer that manages permits; threads call acquire() to obtain a permit (blocking if none are available) and release() to return a permit, with the maximum number of permits defined at creation.
Professional Skill
The core skill of Semaphore is "permit management", which can be used to implement rate‑limiting functionality easily.
What Is Rate Limiting?
Rate limiting controls the maximum number of concurrent accesses to a resource, similar to limiting the number of visitors entering a scenic spot or the number of cars allowed on a road during peak hours, thereby preventing overload and ensuring stable operation.
Implementing Rate Limiting with Semaphore
When a Semaphore is created, the number of permits sets the upper bound for concurrent access. Calls to release() add permits, while acquire() blocks until a permit becomes available, effectively throttling the flow of requests.
Project Example
The following code demonstrates using a Semaphore to limit a parking‑lot to two spaces while five cars (threads) compete for entry.
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* Author: Lei Ge
* By: Java Chinese Community
*/
public class SemaphoreExample {
// Create a semaphore with 2 permits (2 parking spaces)
static Semaphore semaphore = new Semaphore(2);
public static void main(String[] args) {
// Fixed thread pool of 5 threads (5 cars)
ExecutorService threadPool = Executors.newFixedThreadPool(5);
Runnable runnable = new Runnable() {
@Override
public void run() {
String tname = Thread.currentThread().getName();
System.out.println(String.format("Driver: %s, waiting outside, time: %s", tname, new Date()));
try {
Thread.sleep(100); // Simulate arrival delay
semaphore.acquire(); // Block until a permit is available
System.out.println(String.format("Driver: %s, entered parking lot, time: %s", tname, new Date()));
Thread.sleep(1000); // Simulate parking duration
System.out.println(String.format("Driver: %s, leaving parking lot, time: %s", tname, new Date()));
semaphore.release(); // Return the permit
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// Submit five tasks
threadPool.submit(runnable);
threadPool.submit(runnable);
threadPool.submit(runnable);
threadPool.submit(runnable);
threadPool.submit(runnable);
threadPool.shutdown();
}
}Running the program shows that at most two drivers can be inside the parking lot simultaneously; the others wait until a permit is released, thereby achieving rate limiting.
Personal Evaluation
Semaphore provides two modes: fair (FIFO) and non‑fair (default). The non‑fair mode offers higher performance because a newly released permit may be granted to a thread that is ready, without strictly following the arrival order.
Fair vs. Non‑Fair Mode
Fair mode respects the order of acquire() calls (first‑in‑first‑out). Non‑fair mode allows a thread that happens to be ready at the moment of release to obtain the permit, which can improve throughput.
Using Fair Mode
Semaphore offers two constructors; the second one accepts a boolean fair flag. Setting this flag to true creates a fair semaphore.
public Semaphore(int permits) {
sync = new NonfairSync(permits); // default non‑fair
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}Additional Semaphore Methods
int availablePermits(): returns the current number of permits.
int getQueueLength(): returns the number of threads waiting for permits.
boolean hasQueuedThreads(): checks if any threads are waiting.
boolean isFair(): indicates whether the semaphore is fair.
void release(int permits): releases a given number of permits.
boolean tryAcquire(): attempts to acquire a permit without blocking.
boolean tryAcquire(int permits): attempts to acquire the specified number of permits.
boolean tryAcquire(int permits, long timeout, TimeUnit unit): tries to acquire permits within a timeout.
boolean tryAcquire(long timeout, TimeUnit unit): tries to acquire a single permit within a timeout.
void reducePermits(int reduction): protected method to reduce the number of permits.
Collection getQueuedThreads(): protected method returning the waiting threads.
Conclusion
Semaphore manages a set of permits, defaulting to a non‑fair strategy for higher performance. The key methods are release() (to add a permit) and acquire() (to block until a permit is available). By controlling permits, Semaphore provides an effective way to implement rate limiting in concurrent Java applications.
Recommended Reading
Kafka Principles: Illustrated Architecture
Architecture Design Methodology
Kafka from an Interview Perspective
Database and Cache Dual‑Write Consistency
Comprehensive Load Balancing Principles
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
