Upgrade Java read lock to write lock for caching in Map
I have a deadlock situation in the code below:
private static final ReadWriteLock opClassesLock = new ReentrantReadWriteLock();
private static final Map<Class<?>, ServiceClass> opClasses = new WeakHashMap<Class<?>, ServiceClass>();
public static ServiceClass get(Class<?> myClass) {
opClassesLock.readLock().lock();
try {
ServiceClass op = opClasses.get(myClass);
if (op == null) {
opClassesLock.writeLock().lock(); // deadlock here
try {
op = new ServiceClass(myClass);
opClasses.put(myClass, op);
} finally {
opClassesLock.writeLock().unlock();
}
}
return op;
} finally {
开发者_高级运维 opClassesLock.readLock().unlock();
}
}
Had I checked the documentation for ReentrantReadWriteLock
, I could have predicted this:
Reentrancy also allows downgrading from the write lock to a read lock, by acquiring the write lock, then the read lock and then releasing the write lock. However, upgrading from a read lock to the write lock is not possible.
Besides just using a single lock instead of a read/write-lock (which won't allow concurrent reads), is there any other way to solve this kind of problem?
Use a well-tested solution like
new MapMaker().weakKeys().makeMap();
from Guava. You can even do things like
new MapMaker().weakKeys()
.concurrencyLevel(16)
.expireAfterAccess(5, TimeUnit.MINUTES)
.maximumSize(1000)
.makeComputingMap(new Function<Class<?>, ServiceClass>() {
@Override
public ServiceClass apply(Class<?> myClass) {
return new ServiceClass(myClass);
}
});
which should solve your whole problem and offers a lot of possibilities to tune the caching.
The reason for the deadlock is acquiring the write lock while both thread are holding the read lock. Unlike downgrading the lock, upgrading may block. You'd need to release the read lock first.
When you get the write lock, you should test if another thread didn't do the work yet.
I realise now that the example above, if it worked, would not be valid anyway, because this could happen
- Thread 1 sees there is no entry in the map and gets the write lock
- Thread 2 sees there is no entry in the map and gets the write lock (but has to wait)
- Thread 1 adds the entry and releases the write lock
- Thread 2 now adds the entry, but it's already there
So there are two options:
If the entry can't be made twice (because of side effects) use a single lock for the entire method. Or, release the read lock, acquire the write lock, and check again if the entry is still missing before calculating it.
If the entry may be made twice (if it's just a cache) release the read lock before getting the write lock.
I think releasing the read lock before acquiring the write one may work. But the at the exact moment after releasing the read lock, some other threads may take the lock away, which leads to the waiting of the previous thread.
精彩评论