Conditionally lock resources
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Conditionally locking a resource means you do not blindly acquire a lock every time. Instead, you lock only when the state or operation requires exclusive access. Done well, this reduces contention. Done badly, it creates races because the condition is checked outside the protection of the lock.
The Key Rule: Check Shared Conditions Under the Lock
The most important rule is that conditions involving shared mutable state should usually be checked while holding the relevant lock.
Unsafe pattern:
Two threads can observe resourceInUse == false before either enters the synchronized block.
Safer pattern:
The condition and the update must be part of the same critical section if they protect the same shared fact.
Use tryLock() for Opportunistic Access
Sometimes the right meaning of "conditional locking" is "lock only if the resource is immediately available." In Java, ReentrantLock.tryLock() expresses that directly.
This is useful when waiting is worse than skipping or retrying later.
Conditional Waiting Is Different from Conditional Acquisition
Another common case is when a thread must wait until some condition becomes true before using the resource. That is better modeled with a condition variable rather than repeated polling.
Here the lock is always used, but access to the resource is still conditional on the shared state.
Keep Lock Scope Small
Conditionally locking resources is often motivated by performance. A big improvement usually comes from reducing how long the lock is held.
Bad pattern:
- lock resource
- do expensive unrelated work
- unlock resource
Better pattern:
- prepare what you can outside the lock
- lock only for shared-state access
- unlock immediately after the shared update
This does more for throughput than many clever locking tricks.
Lock Ordering Still Matters
If several resources may be locked conditionally, deadlock risk still exists. For example, thread A might lock resource 1 then wait for resource 2, while thread B does the opposite.
A simple preventive rule is to always acquire multiple locks in a fixed order.
Even sophisticated conditional logic does not rescue a design that ignores lock ordering.
Sometimes an Atomic Variable Is Better
If the condition is only a simple state transition, a full lock may be unnecessary. An atomic compare-and-set can model conditional acquisition more directly.
This is not a universal replacement for locks, but for small ownership flags it is often cleaner.
Choose the Semantics You Actually Need
"Conditionally lock" can mean several things:
- lock only if free now
- wait until state becomes valid
- lock only around the shared update
- attempt ownership using an atomic state flag
These are different concurrency problems. Choosing the wrong primitive is often why locking code becomes confusing.
Common Pitfalls
- Checking a shared condition outside the lock and then acting on stale state.
- Using polling loops when a condition variable would express the wait more clearly.
- Holding a lock longer than necessary and turning a small critical section into a bottleneck.
- Using
tryLock()without a clear policy for what should happen on failure. - Forgetting lock-ordering rules when conditionally acquiring multiple resources.
Summary
- Conditional locking is about acquiring or using a resource only when the state requires it.
- Shared-state conditions usually must be checked under the same lock that protects the state.
- '
tryLock()is useful for opportunistic access, while condition variables are better for waiting until a condition becomes true.' - Keep lock scope small and lock ordering consistent.
- Sometimes an atomic state transition is a better fit than a full lock.

