Reputation: 5338
Consider I have a number of string values (dynamically generated), and a thread pool, and I want each string value to be exclusively owned by at most a single thread at any time. So, for instance, if the string "foo" is locked by a thread T1 at some period in time, all other threads waiting to process "foo" (or any other string equal to "foo" in terms of Object.equals()
) must wait until thread T1 releases the lock.
So I want to implement the following interface:
interface Lock {
void lock(@NonNull String key);
void unlock(@NonNull String key);
}
As I said, any thread entering the lock(String)
method should wait (or wait interruptibly, or wait with a time-out) if the key is already locked by some other thread.
And the unlock(String)
method should effectively unlock the key, throwing an exception in two cases:
Apparently, when implementing the above interface, I have to associate a ReentrantLock
instance with any key that is about to get locked, for the whole time it remains locked.
How do I implement the LockByKey
interface? Which concurrency primitives from the java.util.concurrent
package are most suitable for this task?
Upvotes: 0
Views: 96
Reputation: 5338
Provided that ConcurrentHashMap.compute()
is atomic, the accepted answer can be slightly improved using:
public final class StringLockImpl {
private final Map<String, LockObject> map = new ConcurrentHashMap<>();
public void lock(final String key) {
acquire(key).lock();
}
public void unlock(final String key) {
release(key);
}
private Lock acquire(final String key) {
final LockObject lockObject = map.compute(key, (k, v) -> Objects.requireNonNullElseGet(v, LockObject::new).inc());
return lockObject.lock;
}
private void release(final String key) {
map.compute(key, (k, v) -> {
if (v == null) {
throw new IllegalStateException("Not locked by any thread: " + k);
}
try {
v.lock.unlock();
} catch (final IllegalMonitorStateException imse) {
throw new IllegalStateException("Thread [" + Thread.currentThread().getName() + "] not holding the lock: " + k, imse);
}
return v.dec() == 0 ? null : v;
});
}
private static final class LockObject {
private int counter = 0;
private final Lock lock = new ReentrantLock();
LockObject inc() {
counter++;
return this;
}
int dec() {
final int threadCount = --counter;
if (threadCount < 0) {
throw new IllegalStateException("Thread count is negative: " + threadCount);
}
return threadCount;
}
}
}
Upvotes: 1
Reputation: 20544
You need something like this
public class StringLockImpl implements StringLock {
private final Map<String, LockObject> map = new HashMap<>();
private final Lock lock = new ReentrantLock();
@Override
public void lock(String key) {
acquire(key).lock();
}
@Override
public void unlock(String key) {
release(key).unlock();
}
private Lock acquire(String key) {
lock.lock();
try {
LockObject lockObject = map.computeIfAbsent(key, ignore -> new LockObject());
lockObject.inc();
return lockObject.getLock();
} finally {
lock.unlock();
}
}
private Lock release(String key) {
lock.lock();
try {
LockObject lockObject = map.get(key);
if (lockObject.dec()) {
map.remove(key);
}
return lockObject.getLock();
} finally {
lock.unlock();
}
}
private static class LockObject {
private int counter;
@Getter
private final Lock lock = new ReentrantLock();
public void inc() {
counter++;
}
public boolean dec() {
return --counter == 0;
}
}
}
If you search the net, you probably find existing implementation.
Upvotes: 1