Reputation:
I have the following situation: I'm concurrently processing requests that have a given key. I can process any number of requests at the same time, as long as each key in progress is unique.
I am a total rookie with concurrency in Java. There must be some pattern/utility/existing question for this, but I can't figure out what to search for. Hoping somebody could point me in the right direction, or comment on what I have so far.
This class manages the locks:
class LockMap<K> {
private Map<K, Object> locks = new HashMap<>();
void acquireLock(K key) throws InterruptedException {
Object lockObj;
synchronized (locks) {
lockObj = locks.get(key);
if (lockObj == null) lockObj = new Object();
locks.put(key, lockObj);
}
synchronized (lockObj) {
lockObj.wait();
}
}
void releaseLock(K key) {
Object lockObj;
synchronized (locks) {
lockObj = locks.get(key);
locks.remove(key);
}
if (lockObj != null) {
synchronized (lockObj) {
lockObj.notify();
}
}
}
}
Then I use the lock manager like this:
// lockMap is instance of LockMap shared across all threads
void doSomething(K key) {
lockMap.acquireLock(key);
try {
// something
} finally {
lockMap.releaseLock(key);
}
}
Is this the right way to do it?
Upvotes: 3
Views: 3270
Reputation: 13535
Following solution does not locks LockMap and so is extremly parallel. It uses custom-made Locks
to track the moment when they can be deleted, and handles concurrent deletion/creation.
class Lock {
boolean busy=true; // locked state, a thread is working
int waitCount=0; // number of waiting threads
/** returns true if lock succeeded */
synchronized boolean tryLock() throws InterruptedException {
if (busy) {
waitCount++;
} else if (waitCount==0){
// such values mean that the lock is deleted
return false;
} else {
busy=true;
return true;
}
for (;;) {
wait();
if (!busy) {
waitCount--;
busy=true;
return true;
}
}
}
}
class LockMap<K> {
private ConcurrentHashMap<K, Lock> locks = new ConcurrentHashMap<>();
void acquireLock(K key) throws InterruptedException {
for (;;) {
Lock lockObj = locks.get(key);
if (lockObj==null) {
Lock myLockObj = new Lock();
lockObj=locks.putIfAbsent(key, myLockObj);
if (lockObj==null) {
// successfully inserted, and so locked
return;
}
}
// lockObj existed, lock it or wait in queue
if (lockObj.tryLock()) {
return;
}
}
}
void releaseLock(K key) {
Lock lockObj = locks.get(key);
synchronized (lockObj) {
lockObj.busy=false;
if (lockObj.waitCount==0) {
locks.remove(key);
} else {
lockObj.notify();
}
}
}
}
Upvotes: 3
Reputation: 2122
How about this:
create a ConcurrentHashMap<K,Semaphore>
ConcurrentMap<K, Semaphore> myMap = new ConcurrentHashMap<>();
in your doSomething()
method, use the putIfAbsent()
method to of your map to add a semaphore with one permit to the map, only if the key does not exist in the map.
subsequently do a get()
on the key to fetch the semaphore for that key, and then do your stuff. Release the semaphore when done.
void doSomething(K key) {
myMap.putIfAbsent(key, new Semaphore(1));
Semaphore s = myMap.get(myKey);
s.aquire();
try {
// do stuff
} finally {
s.release();
}
}
The only real problem with this scheme is if your list of keys will grow indefinitely, I don't have a good race-condition-free strategy for removing the semaphore from the map. (But if you know you will reuse the same keys over and over, or the list will grow slowly, then maybe this is ok.)
Upvotes: 3