package com.bumptech.glide.load.engine.cache;
import com.bumptech.glide.load.Key;
import com.bumptech.glide.util.Preconditions;
import com.bumptech.glide.util.Synthetic;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Keeps a map of keys to locks that allows locks to be removed from the map when no longer in use
* so the size of the collection is bounded.
*
* <p> This class will be accessed by multiple threads in a thread pool and ensures that the
* number of threads interested in each lock is updated atomically so that when the count reaches
* 0, the lock can safely be removed from the map. </p>
*/
final class DiskCacheWriteLocker {
private final Map<Key, WriteLock> locks = new HashMap<>();
private final WriteLockPool writeLockPool = new WriteLockPool();
void acquire(Key key) {
WriteLock writeLock;
synchronized (this) {
writeLock = locks.get(key);
if (writeLock == null) {
writeLock = writeLockPool.obtain();
locks.put(key, writeLock);
}
writeLock.interestedThreads++;
}
writeLock.lock.lock();
}
void release(Key key) {
WriteLock writeLock;
synchronized (this) {
writeLock = Preconditions.checkNotNull(locks.get(key));
if (writeLock.interestedThreads < 1) {
throw new IllegalStateException("Cannot release a lock that is not held"
+ ", key: " + key
+ ", interestedThreads: " + writeLock.interestedThreads);
}
writeLock.interestedThreads--;
if (writeLock.interestedThreads == 0) {
WriteLock removed = locks.remove(key);
if (!removed.equals(writeLock)) {
throw new IllegalStateException("Removed the wrong lock"
+ ", expected to remove: " + writeLock
+ ", but actually removed: " + removed
+ ", key: " + key);
}
writeLockPool.offer(removed);
}
}
writeLock.lock.unlock();
}
private static class WriteLock {
final Lock lock = new ReentrantLock();
int interestedThreads;
@Synthetic
WriteLock() { }
}
private static class WriteLockPool {
private static final int MAX_POOL_SIZE = 10;
private final Queue<WriteLock> pool = new ArrayDeque<>();
@Synthetic
WriteLockPool() { }
WriteLock obtain() {
WriteLock result;
synchronized (pool) {
result = pool.poll();
}
if (result == null) {
result = new WriteLock();
}
return result;
}
void offer(WriteLock writeLock) {
synchronized (pool) {
if (pool.size() < MAX_POOL_SIZE) {
pool.offer(writeLock);
}
}
}
}
}