Faster, ThreadSafe, TTL supported Cache

package com.example.myapplication.cache;

public class CacheManager {

private static CacheManager mInstance;
private MemoryCache memoryCache;
public static int prefetchThreshold = 100;
public static int expiryThreshold = 120;

private CacheManager() {
memoryCache = new MemoryCache();
DBCache dbCache = new DBCache();
DiskCache diskCache = new DiskCache();
NetworkData networkData = new NetworkData();
memoryCache.setNextChain(dbCache);
memoryCache.setNetworkFetcher(networkData);
dbCache.setNextChain(diskCache);
diskCache.setNextChain(networkData);
}

public static CacheManager getInstance(){
if(mInstance == null){
synchronized (CacheManager.class){
if(mInstance == null){
mInstance = new CacheManager();
}
}
}
return mInstance;
}

public CacheValue getEntry(String key){
return memoryCache.getEntry(key);
}

public void putEntry(String key, CacheValue val){
memoryCache.putEntry(key, val);
}

}
package com.example.myapplication.cache;

import android.graphics.Bitmap;

public class CacheValue {

public Bitmap bm;
public long timeStamp;

CacheValue(Bitmap bm, long timeStamp) {

}

public int getSize() {
return 1;
}
}
package com.example.myapplication.cache;

public interface CacheDataFetcher{
void setNextChain(CacheDataFetcher dataFetcher);
CacheValue getEntry(String key);
void deleteEntry(String key);
void updateEntry(String key, CacheValue cv);
void AddEntryIfNotPresent(String key, CacheValue cv);
}
package com.example.myapplication.cache;

import com.example.myapplication.AppExecutors;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MemoryCache extends LinkedHashMap<String, CacheValue> implements CacheDataFetcher {

private CacheDataFetcher nextChain;
private CacheDataFetcher networkFetcher;
private int maxSize = 2000;
private int currentSize = 0;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public MemoryCache() {
super(10, 0.75f, true);
}

@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

public void setNetworkFetcher(CacheDataFetcher networkFetcher) {
this.networkFetcher = networkFetcher;
}

@Override
protected boolean removeEldestEntry(Map.Entry<String, CacheValue> eldest) {
return currentSize > maxSize;
}

public void putEntry(String key, CacheValue val) {
try {
lock.writeLock().lock();
if (this.containsKey(key)) {
CacheValue value = this.get(key);
if (value != null)
currentSize -= value.getSize();
}
currentSize += val.getSize();
this.put(key, val); //here Linkedhashmap will call removeEldestEntry and removed eldest entry if size exceeds
} catch (Exception ex) {
lock.writeLock().unlock();
} finally {
lock.writeLock().unlock();
}
}

@Override
public CacheValue getEntry(String key) {
try {
lock.readLock().lock();
CacheValue val = this.get(key);
if (val != null && val.bm != null) {
if(System.currentTimeMillis() - val.timeStamp < CacheManager.expiryThreshold) {
checkPrefetchData(val, key);
lock.readLock().unlock();
return val;
}else{
lock.writeLock().lock();
this.remove(key);
nextChain.deleteEntry(key);
lock.writeLock().unlock();
}
}
CacheValue cv2 = nextChain.getEntry(key);
putEntry(key, cv2);
checkPrefetchData(val, key);
lock.readLock().unlock();
return cv2 ;
} catch (Exception ex) {
lock.readLock().unlock();
lock.writeLock().unlock();
} finally {
lock.readLock().unlock();
}
return null;
}

public void checkPrefetchData(CacheValue val, String key){
if (System.currentTimeMillis() - val.timeStamp > CacheManager.prefetchThreshold) {
AppExecutors.getInstance().getIoDispatcherDispatcher().execute(() -> {
CacheValue cv1 = networkFetcher.getEntry(key);
nextChain.updateEntry(key, cv1);
putEntry(key,cv1);
});
}else{
nextChain.AddEntryIfNotPresent(key, cv1);
} }
}
package com.example.myapplication.cache;

import com.example.myapplication.AppExecutors;

public class DBCache implements CacheDataFetcher{

private CacheDataFetcher nextChain;

@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

@Override
public CacheValue getEntry(String key) {
CacheValue val = null;
//val = do some db query if val is null then call next chain
if (val != null && val.bm != null) {
if (System.currentTimeMillis() - val.timeStamp < CacheManager.expiryThreshold) {
return val;
} else {
nextchain.deleteEntry(key); AppExecutors.getInstance().getIoDispatcherDispatcher().execute(()->{
deleteFile(key);
});
}
}
return nextChain.getEntry(key);
}

public void deleteFile(String key){
//do file deletion here from DB
}
public void deleteEntry(String key){
deleteFile(key);
nextChain.deleteEntry(key);
}

public void updateEntry(String key, CacheValue cv){
// update DB with cache value of key
nextChain.updateEntry(key, cv)
}
public void AddEntryIfNotPresent(String key, CacheValue cv){
// add entry in DB file here
nextChain.AddEntryIfNotPresent(key, cv); }}
package com.example.myapplication.cache;

import com.example.myapplication.AppExecutors;

import java.io.File;

public class DiskCache implements CacheDataFetcher{

private CacheDataFetcher nextChain;

@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

@Override
public CacheValue getEntry(String key) {
File f = new File(key);
if (f.exists()) {
CacheValue val = null;
//val = Do file operation to get the data
if (val != null && val.bm != null) {
if (System.currentTimeMillis() - val.timeStamp < CacheManager.expiryThreshold) {
return val;
} else {
nextChain.deleteEntry(key); AppExecutors.getInstance().getIoDispatcherDispatcher().execute(()->{
deleteFile(key, f);
});

}
}
}
return nextChain.getEntry(key);
}

public synchronized void deleteFile(String key, File f){
if(f.exists()) {
//do file deletion here
}
}
public void deleteEntry(String key){
deleteFile(key);
nextChain.deleteEntry(key);
}

public void updateEntry(String key, CacheValue cv){
// update Diskcache with cache value of key
nextChain.updateEntry(key, cv);
}
public void AddEntryIfNotPresent(String key, CacheValue cv){
// add entry in Disk file here
nextChain.AddEntryIfNotPresent(key, cv); }}
package com.example.myapplication.cache;

import android.graphics.Bitmap;

public class NetworkData implements CacheDataFetcher{

private CacheDataFetcher nextChain;
@Override
public void setNextChain(CacheDataFetcher dataFetcher) {
this.nextChain = dataFetcher;
}

@Override
public CacheValue getEntry(String key) {
Bitmap bm = null;
// bm = do some network operation and replace null value
return new CacheValue(bm, System.currentTimeMillis());
}
public void deleteEntry(String key){
// No need to do anything on network
}

public void updateEntry(String key, CacheValue cv){
// no need to do anything on network
}
public void AddEntryIfNotPresent(String key, CacheValue cv){
// nothing required here
}}
package com.example.myapplication;

import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

public class AppExecutors {

private static AppExecutors mInstance;
private final Executor ioDispatcher;//Network,Disk,DB access
private final Executor cpuDispatcher;//CPU consumtion(Sorting)
private final Executor mainDispatcher;

private AppExecutors(){
cpuDispatcher = Executors.newSingleThreadExecutor();
ioDispatcher = Executors.newFixedThreadPool(4);
mainDispatcher = new MainDispatcher();

}

public static AppExecutors getInstance(){
if(mInstance == null){
synchronized (AppExecutors.class){
if(mInstance == null){
mInstance = new AppExecutors();
}
}
}
return mInstance;
}
// Most Thread time goes into waiting , don't consume much CPU
//no of threads may increase optimally since more thread take memory
public Executor getIoDispatcherDispatcher(){
return ioDispatcher;
}
// CPU consumption is more , so take 1-2 threads for this part
public Executor getCPUDispatcher(){
return cpuDispatcher;
}

public Executor getMainDispatcher(){
return mainDispatcher;
}

private static class MainDispatcher implements Executor{
private Handler mainThreadHandler = HandlerCompat.createAsync(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
mainThreadHandler.post(command);
}
}
}

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store