MySQL has a handy function:
SELECT GET_LOCK("SomeName")
This can be used to create simple, but very specific, name based locks for an application. However, it requires a database connection.
I have many situations like:
someMethod() {
// do stuff to user A for their data for feature X
}
It doesn't make sense to simply synchronize this method, because, for example, if this method is called for user B in the meantime, user B does not need to wait for user A to finish before it starts, only operations for the user A and feature X combination need to wait.
With the MySql lock I could do something like:
someMethod() {
executeQuery("SELECT GET_LOCK('userA-featureX')")
// only locked for user A for their data for feature X
executeQuery("SELECT RELEASE_LOCK('userA-featureX')")
}
Since Java locking is based on objects, it seems like I would need to create a new object to represent the situation for this lock and then put it in a static cache somewhere so all the threads can see it. Subsequent requests to lock for that situation would then locate the lock object in the cache and acquire its lock. I tried to create something like this, but then the lock cache itself needs synchronization. Also, it is difficult to detect when a lock object is no longer being used so that it can be removed from the cache.
I have looked at the Java concurrent packages, but nothing stands out as being able to handle something like this. Is there an easy way to implement this, or am I looking at this from the wrong perspective?
Edit:
To clarify, I am not looking to create a predefined pool of locks ahead of time, I would like to create them on demand. Some pseudo code for what I am thinking is:
LockManager.acquireLock(String name) {
Lock lock;
synchronized (map) {
lock = map.get(name);
// doesn't exist yet - create and store
if(lock == null) {
lock = new Lock();
map.put(name, lock);
}
}
lock.lock();
}
LockManager.releaseLock(String name) {
// unlock
// if this was the last hold on the lock, remove it from the cache
}
Similar to the answer from Lyomi, but uses the more flexible ReentrantLock instead of a synchronized block.
Yes this will grow over time - but using the ReentrantLock opens up greater possibilities for removing the lock from the map. Although, removing items from the map doesn't seem all that useful considering removing values from the map will not shrink its size. Some manual map sizing logic would have to be implemented.
In response to the suggestion of using new MapMaker().makeComputingMap()...
MapMaker().makeComputingMap() is deprecated for safety reasons. The successor is CacheBuilder. With weak keys/values applied to CacheBuilder, we're soooo close to a solution.
The problem is a note in CacheBuilder.weakKeys():
This makes it impossible to select an existing lock by string value. Erg.
Memory consideration
Often times, synchronization needed for a particular key is short-lived. Keeping around released keys can lead to excessive memory waste, making it non-viable.
Here's an implementation does not internally keep around released keys.
Reentrancy, and other bells and whistles
If one wants re-entrant lock semantics, they can extend the above by wrapping the mutex object in a class that keeps track of the locking thread and locked count.
e.g.:
If one wants
lock()
to return an object to make releases easier and safer, that's also a possibility.Putting it all together, here's what the class could look like:
And here's how one might use it:
maybe this is useful for you: jkeylockmanager
Edit:
My initial response was probably a bit short. I am the author and was faced with this problem several times and could not find an existing solution. That's why I made this small library on Google Code.