πŸ”₯ Launch Offer: Interview Preparation Bundle @ β‚Ή2,499 β‚Ή9,999 β€’ 75% OFF. Offer expires in πŸ”₯ Launch Offer: Interview Preparation Bundle @ β‚Ή2,499 β‚Ή9,999 β€’ 75% OFF. Offer expires in Interview Preparation Bundle @ β‚Ή2,499 β‚Ή9,999

00:00:00 Get Bundle
home java java-semaphore-complete-guide-with-real-world-examples-code-and-interview-questions
Interview guide Java 44 sections

Java Semaphore: Complete Guide with Real-World Examples, Code, and Interview Questions

Semaphore is one of the most useful concurrency utilities in Java when you want to limit the number of threads that can access a resource at the same…

Start reading
By javainterviewprep June 14, 2026 9 min read

Semaphore is one of the most useful concurrency utilities in Java when you want to limit the number of threads that can access a resource at the same time.


What is Semaphore?

A Semaphore controls access to a shared resource by maintaining a fixed number of permits.

  • If a permit is available β†’ thread proceeds.
  • If no permit is available β†’ thread waits.
  • When work finishes β†’ permit is released.
  • Another waiting thread gets permission.

Think of it as a parking lot.

Parking Lot Capacity = 20

Car arrives
β”‚
β–Ό
Space Available?
β”‚
β”Œβ”€β”€β”€β”΄β”€β”€β”€β”€β”
β”‚ β”‚
Yes No
β”‚ β”‚
Park Wait
β”‚
Leave
β”‚
Space becomes free
β”‚
Next waiting car enters

Real-world Example

Suppose your e-commerce application uses Razorpay/Stripe.

The payment provider allows only 20 concurrent payment requests.

If your application suddenly receives 100 payment requests, sending all 100 together may:

  • exceed API limits
  • cause throttling
  • receive HTTP 429 (Too Many Requests)
  • slow down payment gateway

Instead, you process only 20 simultaneously.

100 Payment Requests

β”‚
β–Ό

Semaphore (20 permits)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 20 Requests Processing β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

80 Waiting

β”‚
β–Ό

One payment completes

Permit released

β”‚
β–Ό

Next waiting request starts

Only 20 are active at any time.


Without Semaphore

Imagine 100 threads.

100 Threads

Thread1
Thread2
Thread3
...
Thread100

↓

All hit Payment Gateway

↓

Gateway Overloaded

↓

Some requests fail

With Semaphore

Semaphore = 20

100 Threads

↓

20 acquire permit

↓

20 processing

↓

80 blocked

↓

One finishes

↓

release()

↓

One waiting thread starts

Concurrency is controlled automatically.


Basic Methods

Semaphore semaphore = new Semaphore(20);

Meaning:

20 permits available

acquire()

Takes one permit.

semaphore.acquire();

If permit exists

Permits = 20

Thread A

↓

Acquire

↓

Permits = 19

If no permit exists

Permits = 0

Thread arrives

↓

Wait

The thread blocks until someone releases a permit.


release()

Returns permit.

semaphore.release();
Permits = 0

Thread finishes

↓

release()

↓

Permits = 1

↓

Waiting thread wakes up

Example Code

import java.util.concurrent.*;

public class PaymentGatewayExample {

private static final Semaphore semaphore = new Semaphore(3);

public static void main(String[] args) {

ExecutorService executor =
Executors.newFixedThreadPool(10);

for (int i = 1; i <= 10; i++) {

int paymentId = i;

executor.submit(() -> processPayment(paymentId));
}

executor.shutdown();
}

private static void processPayment(int paymentId) {

try {

semaphore.acquire();

System.out.println(
"Payment " + paymentId +
" started by " +
Thread.currentThread().getName());

Thread.sleep(3000);

System.out.println(
"Payment " + paymentId +
" completed");

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

} finally {

semaphore.release();
}
}
}

Output

Payment 1 started
Payment 2 started
Payment 3 started

(Only these three)

After 3 seconds

Payment 1 completed

Payment 4 started

Payment 2 completed

Payment 5 started

...

Notice

Even though

10 threads exist

only

3 payments

run simultaneously.

Execution Flow

Initially

Permits = 3

Thread 1

Acquire

Permits = 2

Thread 2

Acquire

Permits = 1

Thread 3

Acquire

Permits = 0

Thread 4

Acquire

↓

No permit

↓

WAITING

Thread 5

WAITING

Thread 1 finishes

release()

Permits = 1

↓

Thread4 wakes up

Acquire

Permits = 0

Again only 3 threads execute.


Why release() should always be inside finally

Suppose payment processing throws an exception.

Wrong

semaphore.acquire();

processPayment();

semaphore.release();

If

processPayment()

throws exception,

release()

never executes.

Permit is permanently lost.

Eventually

Permits = 0

Every thread waits forever.

Correct

try {

semaphore.acquire();

processPayment();

} finally {

semaphore.release();
}

Now permit is always returned.


Real-world Spring Boot Example

Imagine your application receives 1000 orders per second.

Each order needs to call an external fraud detection API.

Order Service

↓

Fraud Detection API

↓

External Vendor

Vendor documentation says:

Maximum Concurrent Requests = 50

Implementation

@Service
public class FraudService {

private final Semaphore semaphore =
new Semaphore(50);

public FraudResult checkFraud(Order order)
throws InterruptedException {

semaphore.acquire();

try {

return fraudClient.check(order);

} finally {

semaphore.release();
}
}
}

No matter how many requests reach your application,

only

50

external API calls

run simultaneously.


Another Example – Database Connection Pool

Suppose

Database Connections = 10

Application receives

200 requests

Only

10

can use database

simultaneously.

Remaining requests wait until a connection is released.

Many connection pool implementations internally use semaphore-like mechanisms to control access.


Another Example – File Upload Server

Suppose image compression is CPU intensive.

You allow only

5

compressions

at a time.
100 uploads

↓

Semaphore(5)

↓

5 compressing

↓

95 waiting

This prevents CPU exhaustion.


Semaphore vs CountDownLatch

FeatureSemaphoreCountDownLatch
PurposeLimit concurrent accessWait until tasks finish
Threads wait forA permitLatch count becomes zero
Reusableβœ… Yes❌ No
Controls concurrencyβœ… Yes❌ No
Blocks worker threads?Yes, when no permits are availableWorker threads usually don’t block; the waiting thread calls await()
Typical useRate limiting, connection pools, API throttlingWait for multiple tasks to complete

Semaphore vs CyclicBarrier

FeatureSemaphoreCyclicBarrier
PurposeLimit concurrent threadsSynchronize phases
Waiting ConditionPermit unavailableAll participating threads reach the barrier
Reusableβœ… Yesβœ… Yes
Controls concurrencyβœ… Yes❌ No
Typical UseExternal API limits, DB connectionsMulti-phase parallel algorithms

When should you use Semaphore?

Use a Semaphore when you need to restrict how many threads can access a shared resource at the same time, such as:

  • βœ… Payment gateway with a concurrency limit
  • βœ… Third-party REST APIs with rate/concurrency restrictions
  • βœ… Database connection pools
  • βœ… File upload or image/video processing
  • βœ… Email or SMS sending services
  • βœ… Printer access (limited printers)
  • βœ… GPU-intensive or CPU-intensive tasks
  • βœ… Any limited-capacity shared resource

Interview Definition

Semaphore is a synchronization utility in Java that controls access to a shared resource by maintaining a fixed number of permits. A thread must acquire a permit before accessing the resource and release it after completion. If no permits are available, additional threads wait until a permit is released. It is commonly used to limit concurrent access to resources such as database connections, payment gateways, and third-party APIs.

Some important interview questions on Semaphore

1. What is a Semaphore in Java?

Answer

A Semaphore is a synchronization utility in Java that controls access to a shared resource using a fixed number of permits.

  • A thread must acquire a permit before accessing the resource.
  • After completing its work, it releases the permit.
  • If no permits are available, the thread waits until one is released.

2. Why do we use Semaphore?

Answer

Semaphore is used to limit the number of threads that can access a resource simultaneously.

Examples:

  • Payment Gateway
  • Database Connections
  • Third-party APIs
  • Printer access
  • File uploads
  • Rate limiting

3. What happens if all permits are occupied?

Example:

Semaphore semaphore = new Semaphore(2);

Thread 1

acquire()

Thread 2

acquire()

Thread 3

acquire()

Answer

Thread 3 blocks until one of the first two threads calls release().


4. What is the difference between acquire() and release()?

Answer

acquire()

  • Takes one permit.
  • Waits if no permit is available.

release()

  • Returns the permit.
  • Wakes one waiting thread (if any).

5. Why should release() be inside finally?

Answer

Because exceptions may occur.

Wrong

semaphore.acquire();

process();

semaphore.release();

If process() throws an exception,

release()

never executes.

Eventually,

All permits are consumed.

Every thread waits forever.

Correct

try {

semaphore.acquire();

process();

} finally {

semaphore.release();
}

6. Can Semaphore have only one permit?

Yes.

Semaphore semaphore = new Semaphore(1);

This behaves similarly to a mutex (mutual exclusion).

Only one thread can access the resource at a time.


7. What is the difference between Semaphore(1) and synchronized?

Semaphore(1)synchronized
Explicit lock managementJVM-managed monitor lock
Can acquire/release in different methods or even different threads (though unusual and error-prone)Lock must be exited by the same thread that entered the synchronized block
Supports timeout (tryAcquire)No timeout
Supports multiple permitsAlways one thread
Can be fairNo fairness guarantee

8. What is a Fair Semaphore?

Semaphore semaphore =
new Semaphore(3, true);

The second parameter

true

means

FIFO order

Threads receive permits in arrival order.

Without fairness

Semaphore semaphore =
new Semaphore(3);

Java may allow newer threads to acquire permits before older waiting threads.


9. What is tryAcquire()?

Instead of waiting,

if (semaphore.tryAcquire()) {

// process

} else {

// reject request
}

If no permit exists,

returns false

instead of blocking.

Useful for

  • API throttling
  • Payment services
  • Fast failure

10. What is tryAcquire(timeout)?

semaphore.tryAcquire(5, TimeUnit.SECONDS);

The thread waits for up to 5 seconds.

If no permit becomes available,

returns false

11. What happens if release() is called more times than acquire()?

Example

Semaphore semaphore = new Semaphore(2);

semaphore.release();

Now permits become

3

Answer

Semaphore does not enforce ownership. Calling release() without a matching acquire() increases the permit count, which can lead to bugs if misused.


12. Is Semaphore reentrant?

No.

Unlike a ReentrantLock, a Semaphore does not track which thread owns a permit.

A thread can call acquire() twice, consuming two permits if available.


13. Is Semaphore thread-safe?

Yes.

Semaphore internally uses Java’s synchronization mechanisms and is safe to use across multiple threads.


14. Can Semaphore be reused?

Yes.

Unlike CountDownLatch, Semaphore can be used repeatedly.

Example

Acquire

↓

Release

↓

Acquire again

↓

Release again

15. Difference between Semaphore and CountDownLatch?

SemaphoreCountDownLatch
Controls concurrent accessWaits for tasks to finish
Uses permitsUses a countdown
ReusableOne-time use
Threads acquire permitsOne or more threads wait on await() until the count reaches zero

16. Difference between Semaphore and CyclicBarrier?

SemaphoreCyclicBarrier
Limits concurrent accessSynchronizes phases
Uses permitsWaits until all participating threads arrive
Threads don’t wait for each other unless permits are exhaustedEvery participating thread waits at the barrier
Used for throttlingUsed for phased computation

17. Can Semaphore be used for rate limiting?

Yes.

Example

API allows

20 concurrent requests.

Use

Semaphore semaphore =
new Semaphore(20);

Only 20 requests execute simultaneously.


18. Where is Semaphore used in Spring Boot?

Common examples:

  • Payment Gateway Integration
  • Email Service
  • SMS Service
  • AWS S3 Upload
  • File Compression
  • External REST APIs
  • Fraud Detection APIs
  • OCR Services
  • AI Model Inference

19. What happens if a thread is interrupted while waiting?

semaphore.acquire();

throws

InterruptedException

Always handle it properly:

try {
semaphore.acquire();
// work
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // restore interrupt status
} finally {
// Only release if a permit was actually acquired
}

A common pattern is to track acquisition:

boolean acquired = false;
try {
semaphore.acquire();
acquired = true;
// work
} finally {
if (acquired) {
semaphore.release();
}
}

20. Give a real-world use case for Semaphore.

Payment Gateway

Gateway Limit

↓

20 concurrent requests

↓

Application receives

100 requests

↓

Semaphore(20)

↓

20 processing

80 waiting

↓

One finishes

↓

Next starts

This protects the payment gateway from being overloaded.


21. Does Semaphore create threads?

No.

Semaphore does not create or manage threads.

It only controls how many existing threads can access a resource at the same time.

Usually it is used together with:

  • ExecutorService
  • ThreadPoolExecutor
  • ForkJoinPool
  • Spring Boot @Async

22. Does Semaphore reduce the number of threads?

No.

Suppose

ExecutorService = 100 threads

Semaphore = 20 permits

There are still 100 threads in the pool.

However,

20 threads

↓

Working

80 threads

↓

Blocked waiting for permit

Semaphore limits concurrent access, not the size of the thread pool.


23. Can Semaphore replace ExecutorService?

No.

Their responsibilities are different:

  • ExecutorService manages the lifecycle and scheduling of threads.
  • Semaphore controls how many of those threads may access a particular resource concurrently.

They are often used together.


24. Which concurrency utility would you choose in these scenarios?

ScenarioUtility
Wait until all tasks completeCountDownLatch
All threads must finish phase 1 before phase 2CyclicBarrier
Only 20 payment requests at a timeSemaphore
Execute asynchronous tasksExecutorService / CompletableFuture
Lock a critical section for one threadsynchronized or ReentrantLock

Interview Tip

A question that interviewers frequently ask is:

“If you’re already using a thread pool of 20 threads, why would you still need a Semaphore with 5 permits?”

A strong answer is:

A thread pool limits the number of worker threads, while a Semaphore limits access to a specific shared resource. A thread may perform many operations, but only one of them (for example, calling a third-party payment API) needs concurrency control. With a 20-thread pool and a Semaphore(5), all 20 threads can continue doing other work, but only 5 are allowed to call the payment service at the same time. This separates thread management from resource access control.