
io.logz.sender.com.bluejeans.common.bigqueue.LRUCache Maven / Gradle / Ivy
package com.bluejeans.common.bigqueue;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simple and thread-safe LRU cache implementation,
* supporting time to live and reference counting for entry.
*
* in current implementation, entry expiration and purge(mark and sweep) is triggered by put operation,
* and resource closing after mark and sweep is done in async way.
*
* @author bulldog
*
* @param key
* @param value
*/
class LRUCache {
private final static Logger logger = LoggerFactory.getLogger(LRUCache.class);
public static final long DEFAULT_TTL = 10 * 1000; // milliseconds
private final Map map;
private final Map ttlMap;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Lock readLock = lock.readLock();
private final Lock writeLock = lock.writeLock();
private static final ExecutorService executorService = Executors.newCachedThreadPool();
private final Set keysToRemove = new HashSet();
public LRUCache() {
map = new HashMap();
ttlMap = new HashMap();
}
/**
* Put a keyed resource with specific ttl into the cache
*
* This call will increment the reference counter of the keyed resource.
*
* @param key the key of the cached resource
* @param value the to be cached resource
* @param ttlInMilliSeconds time to live in milliseconds
*/
public void put(final K key, final V value, final long ttlInMilliSeconds) {
Collection valuesToClose = null;
try {
writeLock.lock();
// trigger mark&sweep
valuesToClose = markAndSweep();
if (valuesToClose != null && valuesToClose.contains(value))
valuesToClose.remove(value);
map.put(key, value);
final TTLValue ttl = new TTLValue(System.currentTimeMillis(), ttlInMilliSeconds);
ttl.refCount.incrementAndGet();
ttlMap.put(key, ttl);
}
finally {
writeLock.unlock();
}
if (valuesToClose != null && valuesToClose.size() > 0) {
if (logger.isDebugEnabled()) {
final int size = valuesToClose.size();
logger.info("Mark&Sweep found " + size + (size > 1 ? " resources" : " resource") + " to close.");
}
// close resource asynchronously
executorService.execute(new ValueCloser(valuesToClose));
}
}
/**
* Put a keyed resource with default ttl into the cache
*
* This call will increment the reference counter of the keyed resource.
*
* @param key the key of the cached resource
* @param value the to be cached resource
*/
public void put(final K key, final V value) {
this.put(key, value, DEFAULT_TTL);
}
/**
* A lazy mark and sweep,
*
* a separate thread can also do this.
*/
private Collection markAndSweep() {
Collection valuesToClose = null;
keysToRemove.clear();
final Set keys = ttlMap.keySet();
final long currentTS = System.currentTimeMillis();
for (final K key : keys) {
final TTLValue ttl = ttlMap.get(key);
if (ttl.refCount.get() <= 0 && currentTS - ttl.lastAccessedTimestamp.get() > ttl.ttl)
keysToRemove.add(key);
}
if (keysToRemove.size() > 0) {
valuesToClose = new HashSet();
for (final K key : keysToRemove) {
final V v = map.remove(key);
valuesToClose.add(v);
ttlMap.remove(key);
}
}
return valuesToClose;
}
/**
* Get a cached resource with specific key
*
* This call will increment the reference counter of the keyed resource.
*
* @param key the key of the cached resource
* @return cached resource if exists
*/
public V get(final K key) {
try {
readLock.lock();
final TTLValue ttl = ttlMap.get(key);
if (ttl != null) {
// Since the resource is acquired by calling thread,
// let's update last accessed timestamp and increment reference counting
ttl.lastAccessedTimestamp.set(System.currentTimeMillis());
ttl.refCount.incrementAndGet();
}
return map.get(key);
}
finally {
readLock.unlock();
}
}
private static class TTLValue {
AtomicLong lastAccessedTimestamp; // last accessed time
AtomicLong refCount = new AtomicLong(0);
long ttl;
public TTLValue(final long ts, final long ttl) {
this.lastAccessedTimestamp = new AtomicLong(ts);
this.ttl = ttl;
}
}
private static class ValueCloser implements Runnable {
Collection valuesToClose;
public ValueCloser(final Collection valuesToClose) {
this.valuesToClose = valuesToClose;
}
@Override
public void run() {
final int size = valuesToClose.size();
for (final V v : valuesToClose)
if (v != null)
CloseCommand.close(v);
if (logger.isDebugEnabled())
logger.debug("ResourceCloser closed " + size + (size > 1 ? " resources." : " resource."));
}
}
public void release(final K key) {
try {
readLock.lock();
final TTLValue ttl = ttlMap.get(key);
if (ttl != null)
// since the resource is released by calling thread
// let's decrement the reference counting
ttl.refCount.decrementAndGet();
}
finally {
readLock.unlock();
}
}
/**
* The size of the cache, equals to current total number of cached resources.
*
* @return the size of the cache
*/
public int size() {
try {
readLock.lock();
return map.size();
}
finally {
readLock.unlock();
}
}
/**
* Remove all cached resource from the cache and close them asynchronously afterwards.
*/
public void removeAll() {
try {
writeLock.lock();
final Collection valuesToClose = new HashSet();
valuesToClose.addAll(map.values());
if (valuesToClose != null && valuesToClose.size() > 0)
// close resource synchronously
for (final V v : valuesToClose)
CloseCommand.close(v);
map.clear();
ttlMap.clear();
}
finally {
writeLock.unlock();
}
}
/**
* Remove the resource with specific key from the cache and close it synchronously afterwards.
*
* @param key the key of the cached resource
* @return the removed resource if exists
*/
public V remove(final K key) {
try {
writeLock.lock();
ttlMap.remove(key);
final V value = map.remove(key);
if (value != null)
// close synchronously
CloseCommand.close(value);
return value;
}
finally {
writeLock.unlock();
}
}
/**
* All values cached
* @return a collection
*/
public Collection getValues() {
try {
readLock.lock();
final Collection col = new ArrayList();
for (final V v : map.values())
col.add(v);
return col;
}
finally {
readLock.unlock();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy