Use Guava Striped
Class
In the past I have had success using the Guava Striped
class for this type of Object
to Lock
mapping operation. For in-depth details on the Striped
class please see the Guava wiki or the Striped
JavaDoc. Essentially, using a Striped<Lock>
instance to index Lock
values via String
keys is a thread-safe way to generate Lock
instances that minimizes memory footprint while maximizing concurrency. If you are avoiding 3rd party libraries you could instead implement your own Map<Object, Lock>
wrapper (perhaps mixing in WeakReference
) to roll your own less full-featured version of the Striped
class (be sure to directly compare hashcodes instead of relying on the ==
operator or the equals
methods!). In either case, the Striped
class is a good place to start learning about how to implement your lock generation/retrieval.
Example
Note that for a specific use case, like for achieving atomic operations for database rows, you might want to use a ReadWriteLock
instead of a simple/basic Lock
(using Semaphore
is also possible). Also, please note that we don't need to intern the String
object given to Striped.get()
because the Striped
class compares Object
's for equality using hashcodes and the String
class makes special guarantees about hashcodes between character equivalent String
s always being equal. This example does use an interned String
(literal String
s are automatically interned) but the Striped
class works perfectly well with a dynamically generated String
which is not interned.
final Striped<Lock> stripedLocks = Striped.lock(10);
final String rowID = "rowLock123";
final Lock rowLock = stripedLocks.get(rowID);
try{
rowLock.lock();//Could also use tryLock or lockInterruptibly methods
//... we are successfully in the fenced/locked code block now
//... put your concurrency sensitive code here
}finally{
rowLock.unlock();
}
Correction
Don't use synchronized
on the returned Lock
object obtained from Striped.get(String)
!
I am adding this explicit warning to not use the returned Lock
object as a synchronized
block's monitor because someone edited my answer to include an example that incorrectly used the returned Lock
as a monitor object.
For reference, this is what that would look like in the above example:
//DO NOT USE THE RETURNED LOCK LIKE THIS
final Lock rowLock = stripedLocks.get(rowID);
synchronized(rowLock){
//...oh no
}
You should not use synchronized
on a Lock
because that defeats the entire purpose of using Lock
! The Lock
class is intended as a replacement for the use of synchronized
blocks. Using them together sacrifices the benefits of Lock
while still having the headaches and gotchas of synchronized
.