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

oracle.nosql.driver.util.LruCache Maven / Gradle / Ivy

There is a newer version: 5.4.16
Show newest version
/*-
 * Copyright (c) 2011, 2020 Oracle and/or its affiliates.  All rights reserved.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 *  https://oss.oracle.com/licenses/upl/
 */
package oracle.nosql.driver.util;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Cache implementation based on a LRU eviction policy.
 * 

* In addition to the general LRU policy the implementation supports expired * entry cleanup when a lookup is performed. If the lifetime of the entry * exceeds the configured maximum entry lifetime, the entry will be removed * from the cache. *

* If eviction is enabled and an entry lifetime is specified, background * clean is launched in a thread that periodically looks for expired entries * and removes them. * * Because this cache supports expiration of entries the value must extend * CacheEntry, which adds time information to the value in the cache. */ public class LruCache { /* * load factor for LinkedHashMap */ private static final float LOAD_FACTOR = 0.6f; /* * Multiply this times the entry lifetime and use that as the sleep * period for the cleanup thread if created. */ private static final int EVICTION_PERIOD_FACTOR = 10; /* The maximum capacity for the cache */ private final int capacity; /* Maximum lifetime for a value entry in ms */ private volatile int lifetime; /* Map of key and value */ private final LinkedHashMap> cacheMap; /* Background expired entry cleanup task */ private TimeBasedCleanupTask cleanupTask; private final ReentrantLock lock = new ReentrantLock(); /** * Constructs an instance of LruCache. There are 2 parameters that control * behavior, capacity and lifetime. If both are 0 the cache is an * ever-expanding structure and entries never age out. If capacity is 0 * then entries are only ever removed because they expire. If lifetime is * 0 then entries are only ever removed because the cache has reached * capacity. * * @param capacity the capacity of the cache. If 0 then there is no limit * to the size and entries are never removed unless they age out. * * @param lifetimeMs the lifetime of a cache entry, in milliseconds. 0 * means entries never expire. */ public LruCache(int capacity, int lifetimeMs) { this.capacity = capacity; this.lifetime = lifetimeMs; if (capacity > 0) { /* * Implement removeEldestEntry to implement the LRU policy when * the cache reaches capacity. */ cacheMap = new LinkedHashMap>(capacity, LOAD_FACTOR, true /* access ordered */) { private static final long serialVersionUID = 1L; @Override protected boolean removeEldestEntry( Map.Entry> entry) { return size() > capacity; } }; } else { cacheMap = new LinkedHashMap>(); } if (lifetime > 0) { /* set up a thread to do cleanup occasionally */ final long evictionPeriodMs = lifetime * EVICTION_PERIOD_FACTOR; cleanupTask = new TimeBasedCleanupTask(evictionPeriodMs); } } /** * Returns the value associated with given key in the cache, or null if * there is no value cached for key. * * @param key the key to use * @return value the value or null if there is no entry for the key */ public V get(K key) { lock.lock(); try { CacheEntry entry = cacheMap.get(key); if (entry == null) { return null; } if (isExpired(entry)) { cacheMap.remove(key); return null; } return entry.getValue(); } finally { lock.unlock(); } } /** * Stores the key value pair in the cache. * * If the cache contains the value associated with the key the old value is * replaced. * * @param key the key to use * @param value the value to use * * @return the old value if there was one present, otherwise null. */ public V put(K key, V value) { lock.lock(); try { CacheEntry entry = cacheMap.put(key, new CacheEntry(value)); if (entry != null) { return entry.getValue(); } return null; } finally { lock.unlock(); } } /** * Removes the cached value for the given key. * * @param key the key to use * @return the previously cached value or null */ public V remove(K key) { lock.lock(); try { CacheEntry entry = cacheMap.remove(key); if (entry != null) { return entry.getValue(); } return null; } finally { lock.unlock(); } } /** * Returns the creation time in milliseconds since the Epoch if available * for the entry specified by the key. If there is no entry for the key * 0 is returned. * * @param key the key to use * @return the creation time, or 0 if no entry is found for the key */ public long getCreationTime(K key) { lock.lock(); try { CacheEntry entry = cacheMap.get(key); if (entry != null) { return entry.getCreateTime(); } return 0L; } finally { lock.unlock(); } } /** * Returns the capacity of the cache * * @return the capacity in number of entries */ public int getCapacity() { return this.capacity; } /** * Returns a set of all values in the cache. The values are live from the * cache. * * @return all values. */ public Set getAllValues() { lock.lock(); try { final Set copy = new HashSet(); for (CacheEntry entry : cacheMap.values()) { copy.add(entry.getValue()); } return copy; } finally { lock.unlock(); } } /** * Stops all cache background tasks. This will only affect a cache with * a non-zero lifetime set for entries. * * @param wait set to true to wait for the background tasks to finish */ public void stop(boolean wait) { if (cleanupTask != null) { cleanupTask.stop(wait); } } private boolean isExpired(CacheEntry entry) { final long now = System.currentTimeMillis(); if (lifetime > 0) { if (now > (entry.getCreateTime() + lifetime)) { return true; } } return false; } /** * The cache entry object. * * The base class for value objects for caches constructed by CacheBuilder. * It maintains the creation time of the entry to support a lifetime * expiration mechanism. */ private static class CacheEntry { /* The time at which the entry was created */ private final long createTime; private final V value; private CacheEntry(V value) { this.value = value; this.createTime = System.currentTimeMillis(); } public long getCreateTime() { return createTime; } private V getValue() { return value; } } /** * Background time-based cleanup. * * Once cache instance enable the background cleanup, this task will be * launched periodically to look up expired value entry and removed from * cache. * * Notes that the background cleanup task is intensive, since it is aim to * look up and remove all expired value entries. */ private class TimeBasedCleanupTask implements Runnable { private volatile boolean terminated = false; private final long cleanupPeriodMs; private final Thread cleanUpThread; TimeBasedCleanupTask(final long cleanupPeriodMs) { this.cleanupPeriodMs = cleanupPeriodMs; cleanUpThread = new Thread(this, "CacheCleanUpThread"); cleanUpThread.setDaemon(true); cleanUpThread.start(); } /** * Attempt to stop the background activity for the cleanup. * @param wait if true, the the method attempts to wait for the * background thread to finish background activity. */ void stop(boolean wait) { /* Set the flag to notify the run loop that it should exit */ terminated = true; cleanUpThread.interrupt(); if (wait) { try { cleanUpThread.join(); } catch (InterruptedException ie) /* CHECKSTYLE:OFF */ { } /* CHECKSTYLE:ON */ } } @Override public void run() { while (true) { if (terminated) { return; } try { Thread.sleep(cleanupPeriodMs); } catch (InterruptedException e) /* CHECKSTYLE:OFF */ { } /* CHECKSTYLE:ON */ if (terminated) { return; } cleanup(); } } /** * Cleanup the expired entry from cache. */ void cleanup() { if (!lock.tryLock()) { return; } try { final Iterator>> iter = cacheMap.entrySet().iterator(); while (iter.hasNext()) { final Map.Entry> entry = iter.next(); if (isExpired(entry.getValue())) { iter.remove(); } } } finally { lock.unlock(); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy