Tuesday 17 May 2016

Create a Simple InMemoryCache (Lightweight Cache) in Java

A cache is an area of local memory that holds a copy of frequently accessed data that is otherwise expensive to get or compute.
Examples: Database Query, disk file or data report.

Cache will keep most recently used items if you will try to add more items then max specified then the least used items will removed which are expired.

We are using org.apache.commons.collections.map.LRUMap, which removes the least used entries from a fixed sized map.

Items will expire based on a time to live period. We created a separate daemon thread for the expiration of items, timestamp the last access and in a separate thread remove the items when the time to live limit is reached. We can reduce the memory pressure for applications that have long idle time in between accessing the cached objects.

InMemoryCacheMap.java
import java.util.ArrayList;
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.map.LRUMap;
/**
 * @author Rajesh.dixit
 */
public class InMemoryCacheMap<K, T> {

    private long timeToLive;
    private LRUMap cacheMap;


    /**
     * Class to store the object with last access time of object.
     * */
    protected class CacheObject {
        public long lastAccessed = System.currentTimeMillis();
        public T value;

        protected CacheObject(T value) {
            this.value = value;
        }
    }

    /**
     * InMemoryCacheMap Constructor to create cache.
     * @param timeToLive - time to live cache in memory.
     * @param timerInterval
     * @param maxItems
     */
    public InMemoryCacheMap(long timeToLive,long timerInterval,int maxItems) {

        this.timeToLive = timeToLive;

        cacheMap = new LRUMap(maxItems);

        if (timeToLive > 0 && timerInterval > 0) {

            /** Clean up Daemon thread which will remove
             *  clear the cache after cross time interval.*/
            Thread t = new Thread(new Runnable() {
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(timerInterval);
                        } catch (InterruptedException ex) {
                        }
                        cleanup();
                    }
                }
            });

            t.setDaemon(true);
            t.start();
        }
    }

    @SuppressWarnings("unchecked")
    public T get(K key) {
        synchronized (cacheMap) {
            CacheObject c = (CacheObject) cacheMap.get(key);

            if (c == null)
                return null;
            else {
                c.lastAccessed = System.currentTimeMillis();
                return c.value;
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void cleanup() {

        long now = System.currentTimeMillis();
        ArrayList<K> deleteKey = null;

        synchronized (cacheMap) {

            MapIterator itr = cacheMap.mapIterator();
            deleteKey = new ArrayList<K>((cacheMap.size() / 2) + 1);

            K key = null;
            CacheObject cachObj = null;

            while (itr.hasNext()) {
                key = (K) itr.next();
                cachObj = (CacheObject) itr.getValue();

                /** Identify the data which crossed the threshold. */
                if (cachObj!=null&&(now>(timeToLive+cachObj.lastAccessed))) {
                    deleteKey.add(key);
                }
            }
        }

        /** Remove the keys which crossed the threshold time. */
        for (K key : deleteKey) {
            synchronized (cacheMap) {
                cacheMap.remove(key);
            }
            Thread.yield();
        }
    }

    public void remove(K key) {
        synchronized (cacheMap) {
            cacheMap.remove(key);
        }
    }

    public int size() {
        synchronized (cacheMap) {
            return cacheMap.size();
        }
    }

    public void put(K key, T value) {
        synchronized (cacheMap) {
            cacheMap.put(key, new CacheObject(value));
        }
    }

    @Override
    public String toString() {
        MapIterator itr = cacheMap.mapIterator();
        StringBuilder sb = new StringBuilder();
        int count = 0;
        while(itr.hasNext()) {
            K key = (K) itr.next();
            CacheObject c = (CacheObject) itr.getValue();
            T value = (T)c.value;
            String info = key+"="+value;
            if(count>0) {
                sb.append(",");
            }
            sb.append(info);
            count++;
        }
        sb.insert(0, "[");
        sb.insert(sb.length(), "]");
        return sb.toString();
    }
}

AddRemoveObjectsTest.java
public class AddRemoveObjectsTest {

      public static void main(String[] args) throws InterruptedException {
            System.out.println("Test: TestAddRemoveObjects: \n");
            AddRemoveObjectsTest.addRemoveObjectsTest();

      }

      private static void addRemoveObjectsTest() throws InterruptedException {


            long timeToLiveInSeconds = 100;
            long timerIntervalInSeconds = 1000; // miliseconds
            int maxItems = 5;

            InMemoryCacheMap<String, String> cache =
                              new InMemoryCacheMap<String,String>
                                    (timeToLiveInSeconds,timerIntervalInSeconds,maxItems);

            cache.put("Report1", "Report1");
            cache.put("Report2", "Report2");
            cache.put("Report3", "Report3");
            cache.put("Report4", "Report4");
            cache.put("Report5", "Report5");

            /* Check the cache element in size.*/
            System.out.println("5 Cache Object Added..");
            System.out.println("Cache Size: " + cache.size());
            System.out.println(cache);


            /* Remove an element from cache manually.*/
            cache.remove("Report5");
            System.out.println("One object removed...");       
            System.out.println("Cache Size: " + cache.size());
            System.out.println(cache);


            /* Add two elements while cache memory has space for one.*/
            cache.put("Report6", "Report6");
            cache.put("Report7", "Report7");
            System.out.println("Two objects Added but reached maxItems so " +
            "remove some old objects from cache...");
            System.out.println("Cache Size: " + cache.size());
            System.out.println(cache);

            /* Perform operation some time later on cacha memory.*/
            Thread.sleep(1000);

            /* Cache will be cleared by daemon thread as it will keep checking
             * the time to live of each element after a given timerInterval.*/
            cache.put("Report8", "Report8");
            cache.put("Report9", "Report9");
            System.out.println("Two objects Added but reached maxItems so " +
            "remove some old objects from cache...");
            System.out.println("Cache Size: " + cache.size());
            System.out.println(cache);
      }
}
Output:
Test: TestAddRemoveObjects:

5 Cache Object Added..
Cache Size: 5
[Report1=Report1,Report2=Report2,Report3=Report3,Report4=Report4,Report5=Report5]
One object removed...
Cache Size: 4
[Report1=Report1,Report2=Report2,Report3=Report3,Report4=Report4]
Two objects Added but reached maxItems so remove some old objects from cache...
Cache Size: 5
[Report2=Report2,Report3=Report3,Report4=Report4,Report6=Report6,Report7=Report7]
Two objects Added but reached maxItems so remove some old objects from cache...
Cache Size: 2
[Report8=Report8,Report9=Report9]


ExpiredCacheObjectsTest.java
public class ExpiredCacheObjectsTest {

      /**
       * @param args
       * @throws InterruptedException
       */
      public static void main(String[] args) throws InterruptedException {
            System.out.println("Test: ExpiredCacheObjectsTest...");
            ExpiredCacheObjectsTest.expiredCacheObjectsTest();

      }

      private static void expiredCacheObjectsTest() throws InterruptedException {

            InMemoryCacheMap<String, String> cache = new InMemoryCacheMap<String, String>(1, 1, 10);

            cache.put("Report1", "Report1");
            cache.put("Report2", "Report2");
            System.out.println("Cache size: "+cache.size()+" Data:" + cache);

            /** After 1 Second, both the reports able to removed.
             * Wait for 4 second to print cache data*/
            Thread.sleep(3000);
            System.out.println("After 3 seconds…");

            System.out.println("Cache size: "+cache.size()+" Data:" + cache);

      }

}

Output:
Test: ExpiredCacheObjectsTest...
Cache size: 2 Data:[Report1=Report1,Report2=Report2]
After 3 seconds...
Cache size: 0 Data:[]

No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...