io.logz.sender.org.ikasan.bigqueue.cache.LRUCacheImpl Maven / Gradle / Ivy
The newest version!
package org.ikasan.bigqueue.cache;
import java.io.Closeable;
import java.io.IOException;
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&sweep) is triggered by put operation,
* and resource closing after mark&sweep is done in async way.
*
* @author bulldog
*
* @param key
* @param value
*/
public class LRUCacheImpl implements ILRUCache {
private final static Logger logger = LoggerFactory.getLogger(LRUCacheImpl.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 LRUCacheImpl() {
map = new HashMap();
ttlMap = new HashMap();
}
/**
* Shutdown the internal ExecutorService,
*
* Call this only after you have closed your bigqueue instance.
*/
public static void CloseExecutorService() {
executorService.shutdown();
}
public void put(K key, V value, long ttlInMilliSeconds) {
Collection valuesToClose = null;
try {
writeLock.lock();
// trigger mark&sweep
valuesToClose = markAndSweep();
if (valuesToClose != null && valuesToClose.contains(value)) { // just be cautious
valuesToClose.remove(value);
}
map.put(key, value);
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()) {
int size = valuesToClose.size();
logger.info("Mark&Sweep found " + size + (size > 1 ? " resources":" resource") + " to close.");
}
// close resource asynchronously
executorService.execute(new ValueCloser(valuesToClose));
}
}
public void put(K key, 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();
Set keys = ttlMap.keySet();
long currentTS = System.currentTimeMillis();
for(K key: keys) {
TTLValue ttl = ttlMap.get(key);
if (ttl.refCount.get() <= 0 && (currentTS - ttl.lastAccessedTimestamp.get()) > ttl.ttl) { // remove object with no reference and expired
keysToRemove.add(key);
}
}
if (keysToRemove.size() > 0) {
valuesToClose = new HashSet();
for(K key : keysToRemove) {
V v = map.remove(key);
valuesToClose.add(v);
ttlMap.remove(key);
}
}
return valuesToClose;
}
public V get(K key) {
try {
readLock.lock();
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(long ts, long ttl) {
this.lastAccessedTimestamp = new AtomicLong(ts);
this.ttl = ttl;
}
}
private static class ValueCloser implements Runnable {
Collection valuesToClose;
public ValueCloser(Collection valuesToClose) {
this.valuesToClose = valuesToClose;
}
public void run() {
int size = valuesToClose.size();
for(V v : valuesToClose) {
try {
if (v != null) {
v.close();
}
} catch (IOException e) {
// close quietly
}
}
if (logger.isDebugEnabled()) {
logger.debug("ResourceCloser closed " + size + (size > 1 ? " resources.":" resource."));
}
}
}
public void release(K key) {
try {
readLock.lock();
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();
}
}
public int size() {
try {
readLock.lock();
return map.size();
} finally {
readLock.unlock();
}
}
@Override
public void removeAll() throws IOException {
try {
writeLock.lock();
Collection valuesToClose = new HashSet();
valuesToClose.addAll(map.values());
if (valuesToClose != null && valuesToClose.size() > 0) {
// close resource synchronously
for(V v : valuesToClose) {
v.close();
}
}
map.clear();
ttlMap.clear();
} finally {
writeLock.unlock();
}
}
@Override
public V remove(K key) throws IOException {
try {
writeLock.lock();
ttlMap.remove(key);
V value = map.remove(key);
if (value != null) {
// close synchronously
value.close();
}
return value;
} finally {
writeLock.unlock();
}
}
@Override
public Collection getValues() {
try {
readLock.lock();
Collection col = new ArrayList();
for(V v : map.values()) {
col.add(v);
}
return col;
} finally {
readLock.unlock();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy