All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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