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

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