How does tryAcquire() differ from acquire(), and when would you use the timed or multi-permit variants?
answer
- acquire blocks; tryAcquire returns boolean now; timed waits up to limit
- tryAcquire() = fail-fast load shedding / back-pressure
- Untimed tryAcquire barges; timed tryAcquire honours fairness
- acquire(n) is all-or-wait, atomic; n-permit can be starved without fairness
- Only release if tryAcquire returned true
basics
~20 sacquire() waits until a permit is free. tryAcquire() takes a permit only if one is available right now and returns true; otherwise it returns false immediately without waiting. tryAcquire(timeout) waits up to a limit before giving up.
solid answer
~40 sacquire() blocks indefinitely until a permit is available (or the thread is interrupted). tryAcquire() is non-blocking: it grabs a permit and returns true if one is immediately free, otherwise returns false at once — ideal for fail-fast load shedding where you'd rather reject work than queue it. tryAcquire(timeout, unit) is the middle ground: it waits up to the timeout, returning false if still none free, which bounds how long a request can stall. All three have multi-permit forms — acquire(n)/tryAcquire(n) take n permits atomically (you get all or wait/none), useful when a unit of work consumes several resources at once. A subtlety: the *untimed* tryAcquire() ignores fairness and barges even on a fair semaphore, whereas the timed tryAcquire(n, timeout, unit) honours fairness ordering.
go deeper
Knows acquire() waits and tryAcquire() returns true/false without waiting, and that you release in finally only when you got a permit.
Explains the timed tryAcquire and multi-permit forms, and uses tryAcquire for fail-fast/back-pressure decisions.
Reasons about the barging-vs-fairness subtlety of untimed tryAcquire, atomic all-or-nothing acquire(n), and the over-release bug after a failed tryAcquire.
Designs throttling/back-pressure policies choosing among the variants, accounts for starvation of multi-permit acquirers, and ties timeouts to system-level latency SLAs.
## The three ways to take a permit A `Semaphore` offers a spectrum of acquisition strategies, trading **waiting** against **certainty of progress**. ### 1. `acquire()` — wait as long as it takes ```java sem.acquire(); // blocks until a permit is free OR the thread is interrupted (throws InterruptedException) ``` The thread parks until a permit becomes available. Use this when the work *must* eventually run and queuing is acceptable. The blocked thread consumes no CPU (it isn't spinning) but it *is* tied up. There is also `acquireUninterruptibly()` which ignores interruption. ### 2. `tryAcquire()` — take it or leave it, right now ```java if (sem.tryAcquire()) { try { doWork(); } finally { sem.release(); } } else { rejectFast(); // no capacity — shed load instead of queuing } ``` `tryAcquire()` returns `true` and grabs a permit *only* if one is free at that instant; otherwise it returns `false` **immediately**. This is the foundation of **load shedding** / **back-pressure**: under overload you reject (return 503, drop the request, fall back) rather than letting a queue grow unboundedly and exhaust memory or blow latency SLAs. **Important subtlety:** the no-arg, untimed `tryAcquire()` **barges** — it will snatch an available permit even if other threads have been waiting longer, *ignoring the fairness setting*. So on a fair semaphore, `tryAcquire()` can still jump the queue. If you need fairness-respecting non-blocking-ish behaviour, use the timed form. ### 3. `tryAcquire(timeout, unit)` — wait, but not forever ```java if (sem.tryAcquire(200, TimeUnit.MILLISECONDS)) { try { doWork(); } finally { sem.release(); } } else { timeoutFallback(); } ``` Waits up to the timeout, then gives up and returns `false`. This **bounds tail latency**: a request can stall at most `timeout` before failing over. The timed variant **honours fairness** (it queues like `acquire()` and respects FIFO on a fair semaphore). It also throws `InterruptedException` if the thread is interrupted while waiting. ## Multi-permit variants Every form has an n-permit version: `acquire(n)`, `tryAcquire(n)`, `tryAcquire(n, timeout, unit)`, `release(n)`. - `acquire(n)` waits until **all n** permits are available, then takes them **atomically** — you never get a partial allocation. - `release(n)` returns n at once and may unblock several waiters. Use multi-permit when one logical task consumes several units of the bounded resource — e.g. a bulk export that needs 3 worker slots, or weighting larger requests to cost more permits so big jobs naturally limit their own concurrency. **Watch out:** `acquire(n)` on a non-fair semaphore can be starved by a steady stream of single-permit acquirers that keep the count just below n. Fairness mitigates this. ## Choosing | Need | Use | |---|---| | Work must run, queuing OK | `acquire()` | | Reject immediately if busy (load shed) | `tryAcquire()` | | Bound the wait, then fall back | `tryAcquire(timeout, unit)` | | Task needs several units atomically | `acquire(n)` / `tryAcquire(n, …)` | In all cases, pair the successful acquire with a `release` in `finally` — and **only release if you actually acquired** (a common bug is releasing in `finally` after a `tryAcquire` that returned `false`).
- Why is tryAcquire() useful for back-pressure?Because it lets you reject or fall back the instant capacity is exhausted instead of queuing. That keeps memory and latency bounded under overload — you fail fast (e.g. 503) rather than building an unbounded backlog that eventually collapses the service.
- Does tryAcquire() honour the fairness flag?The untimed no-arg tryAcquire() does NOT — it barges and grabs any free permit ahead of waiters. The timed tryAcquire(timeout, unit) does honour fairness, queuing in FIFO order on a fair semaphore.
saying these in an interview costs you the question
- Releasing in finally even when tryAcquire() returned false — over-releases and inflates permits
- Assuming tryAcquire() respects fairness — the untimed form barges
- Thinking acquire(n) can return a partial set of permits
- Using acquire() everywhere and then wondering why latency is unbounded under load