I need to ensure that multiple threads aren't trying to access the same resource at the same time. I have a bunch of these resources, so I want to have a separate lock object for each resource (rather than one global lock) so that threads aren't unnecessarily blocking eachother.
Eddie presents a great solution for this using ConcurrentMap.putIfAbsent()
in https://stackoverflow.com/a/659939/82156.
// From https://stackoverflow.com/a/659939/82156
public Page getPage(Integer id) {
Page p = cache.get(id);
if (p == null) {
synchronized (getCacheSyncObject(id)) {
p = getFromDataBase(id);
cache.store(p);
}
}
}
private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>();
private Object getCacheSyncObject(final Integer id) {
locks.putIfAbsent(id, id);
return locks.get(id);
}
However, one problem with that implementation is that the hashmap will grow unbounded. Is there a way to remove the hash key when the thread is complete without screwing up the concurrency?
Pyrce made me realize that it wasn't required that every resource have its own lock object. Rather, I could use a pool of, say, 100 lock objects that could be shared between the resources. That way, the set of lock objects wouldn't grow unbounded, but I'd still get most of the concurrency benefits I was hoping to get by removing the single global lock.
This meant that I no longer needed to use a ConcurrentHashMap
and could instead just use a simple array that was eagerly initialized.
// getPage() remains unchanged
public Page getPage(Integer id) {
Page p = cache.get(id);
if (p == null) {
synchronized (getCacheSyncObject(id)) {
p = getFromDataBase(id);
cache.store(p);
}
}
}
private int MAX_LOCKS = 100;
private Object[] locks = new Object[MAX_LOCKS];
{
for(int i=0; i<locks.length; ++i)
locks[i] = new Object();
}
private Object getCacheSyncObject(final Integer id) {
return locks[ id % MAX_LOCKS ];
}