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

org.mapdb.Caches Maven / Gradle / Ivy

The newest version!
package org.mapdb;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Contains various instance cache implementations
 */
public final class Caches {

    private Caches(){}


    /**
     * Least Recently Used cache.
     * If cache is full it removes less used items to make a space
     */
    public static class LRU extends EngineWrapper {


        protected LongMap cache;

        protected final ReentrantLock[] locks = new ReentrantLock[CC.CONCURRENCY];
        {
            for(int i=0;i(cacheSize, (int) (cacheSize*0.8)));
        }

        public LRU(Engine engine, LongMap cache){
            super(engine);
            this.cache = cache;
        }

        @Override
        public  long put(A value, Serializer serializer) {
            long recid =  super.put(value, serializer);
            final LongMap cache2 = checkClosed(cache);
            final Lock lock  = locks[Store.lockPos(recid)];
            lock.lock();
            try{
                cache2.put(recid, value);
            }finally {
                lock.unlock();
            }
            return recid;
        }

        @SuppressWarnings("unchecked")
        @Override
        public  A get(long recid, Serializer serializer) {
            final LongMap cache2 = checkClosed(cache);
            Object ret = cache2.get(recid);
            if(ret!=null)
                return (A) ret;

            final Lock lock  = locks[Store.lockPos(recid)];
            lock.lock();
            try{
                ret = super.get(recid, serializer);
                if(ret!=null)
                    cache2.put(recid, ret);
                return (A) ret;
            }finally {
                lock.unlock();
            }
        }

        @Override
        public  void update(long recid, A value, Serializer serializer) {
            final LongMap cache2 = checkClosed(cache);

            final Lock lock  = locks[Store.lockPos(recid)];
            lock.lock();
            try{
                cache2.put(recid, value);
                super.update(recid, value, serializer);
            }finally {
                lock.unlock();
            }
        }

        @Override
        public  void delete(long recid, Serializer serializer){
            final LongMap cache2 = checkClosed(cache);

            final Lock lock  = locks[Store.lockPos(recid)];
            lock.lock();
            try{
                cache2.remove(recid);
                super.delete(recid,serializer);
            }finally {
                lock.unlock();
            }
        }

        @Override
        public  boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) {
            Engine engine = getWrappedEngine();
            LongMap cache2 = checkClosed(cache);

            final Lock lock  = locks[Store.lockPos(recid)];
            lock.lock();
            try{
                Object oldValue = cache2.get(recid);
                if(oldValue == expectedOldValue || (oldValue!=null&&oldValue.equals(expectedOldValue))){
                    //found matching entry in cache, so just update and return true
                    cache2.put(recid, newValue);
                    engine.update(recid, newValue, serializer);
                    return true;
                }else{
                    boolean ret = engine.compareAndSwap(recid, expectedOldValue, newValue, serializer);
                    if(ret) cache2.put(recid, newValue);
                    return ret;
                }
            }finally {
                lock.unlock();
            }
        }


        @Override
        public void close() {
            cache = null;
            super.close();
        }

        @Override
        public void rollback() {
            //TODO locking here?
            checkClosed(cache).clear();
            super.rollback();
        }

        @Override
        public void clearCache() {
            cache.clear();
            super.clearCache();
        }
    }

    /**
     * Fixed size cache which uses hash table.
     * Is thread-safe and requires only minimal locking.
     * Items are randomly removed and replaced by hash collisions.
     * 

* This is simple, concurrent, small-overhead, random cache. * * @author Jan Kotek */ public static class HashTable extends EngineWrapper implements Engine { protected final ReentrantLock[] locks = new ReentrantLock[CC.CONCURRENCY]; { for(int i=0;i long put(A value, Serializer serializer) { final long recid = getWrappedEngine().put(value, serializer); HashItem[] items2 = checkClosed(items); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ items2[position(recid)] = new HashItem(recid, value); }finally{ lock.unlock(); } return recid; } @Override @SuppressWarnings("unchecked") public A get(long recid, Serializer serializer) { final int pos = position(recid); HashItem[] items2 = checkClosed(items); HashItem item = items2[pos]; if(item!=null && recid == item.key) return (A) item.val; Engine engine = getWrappedEngine(); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ //not in cache, fetch and add final A value = engine.get(recid, serializer); if(value!=null) items2[pos] = new HashItem(recid, value); return value; }finally{ lock.unlock(); } } private int position(long recid) { return LongHashMap.longHash(recid^hashSalt)&cacheMaxSizeMask; } @Override public void update(long recid, A value, Serializer serializer) { final int pos = position(recid); HashItem[] items2 = checkClosed(items); HashItem item = new HashItem(recid,value); Engine engine = getWrappedEngine(); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ items2[pos] = item; engine.update(recid, value, serializer); }finally { lock.unlock(); } } @Override public boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) { final int pos = position(recid); HashItem[] items2 = checkClosed(items); Engine engine = getWrappedEngine(); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ HashItem item = items2[pos]; if(item!=null && item.key == recid){ //found in cache, so compare values if(item.val == expectedOldValue || item.val.equals(expectedOldValue)){ //found matching entry in cache, so just update and return true items2[pos] = new HashItem(recid, newValue); engine.update(recid, newValue, serializer); return true; }else{ return false; } }else{ boolean ret = engine.compareAndSwap(recid, expectedOldValue, newValue, serializer); if(ret) items2[pos] = new HashItem(recid, newValue); return ret; } }finally { lock.unlock(); } } @Override public void delete(long recid, Serializer serializer){ final int pos = position(recid); HashItem[] items2 = checkClosed(items); Engine engine = getWrappedEngine(); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ engine.delete(recid,serializer); HashItem item = items2[pos]; if(item!=null && recid == item.key) items[pos] = null; }finally { lock.unlock(); } } @Override public void close() { super.close(); //dereference to prevent memory leaks items = null; } @Override public void rollback() { //TODO lock all in caches on rollback/commit? for(int i = 0;iSoftReference or WeakReference * Items can be removed from cache by Garbage Collector if * * @author Jan Kotek */ public static class WeakSoftRef extends EngineWrapper implements Engine { protected final ReentrantLock[] locks = new ReentrantLock[CC.CONCURRENCY]; { for(int i=0;i extends WeakReference implements CacheItem { final long recid; public CacheWeakItem(A referent, ReferenceQueue q, long recid) { super(referent, q); this.recid = recid; } @Override public long getRecid() { return recid; } } protected static final class CacheSoftItem extends SoftReference implements CacheItem { final long recid; public CacheSoftItem(A referent, ReferenceQueue q, long recid) { super(referent, q); this.recid = recid; } @Override public long getRecid() { return recid; } } @SuppressWarnings("rawtypes") protected ReferenceQueue queue = new ReferenceQueue(); protected Thread queueThread = new Thread("MapDB GC collector"){ @Override public void run(){ runRefQueue(); } }; protected LongConcurrentHashMap items = new LongConcurrentHashMap(); final protected boolean useWeakRef; public WeakSoftRef(Engine engine, boolean useWeakRef){ super(engine); this.useWeakRef = useWeakRef; queueThread.setDaemon(true); queueThread.start(); } /** Collects items from GC and removes them from cache */ protected void runRefQueue(){ try{ final ReferenceQueue queue = this.queue; if(queue == null)return; final LongConcurrentHashMap items = this.items; while(true){ CacheItem item = (CacheItem) queue.remove(); items.remove(item.getRecid(), item); if(Thread.interrupted()) return; } }catch(InterruptedException e){ //this is expected, so just silently exit thread } } @Override public long put(A value, Serializer serializer) { long recid = getWrappedEngine().put(value, serializer); ReferenceQueue q = checkClosed(queue); LongConcurrentHashMap items2 = checkClosed(items); CacheItem item = useWeakRef? new CacheWeakItem(value, q, recid) : new CacheSoftItem(value, q, recid); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ items2.put(recid,item); }finally{ lock.unlock(); } return recid; } @SuppressWarnings("unchecked") @Override public A get(long recid, Serializer serializer) { LongConcurrentHashMap items2 = checkClosed(items); CacheItem item = items2.get(recid); if(item!=null){ Object o = item.get(); if(o == null) items2.remove(recid); else{ return (A) o; } } Engine engine = getWrappedEngine(); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ Object value = engine.get(recid, serializer); if(value!=null){ ReferenceQueue q = checkClosed(queue); item = useWeakRef? new CacheWeakItem(value, q, recid) : new CacheSoftItem(value, q, recid); items2.put(recid,item); } return (A) value; }finally{ lock.unlock(); } } @Override public void update(long recid, A value, Serializer serializer) { Engine engine = getWrappedEngine(); ReferenceQueue q = checkClosed(queue); LongConcurrentHashMap items2 = checkClosed(items); CacheItem item = useWeakRef? new CacheWeakItem(value, q, recid) : new CacheSoftItem(value, q, recid); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ items2.put(recid,item); engine.update(recid, value, serializer); }finally { lock.unlock(); } } @Override public void delete(long recid, Serializer serializer){ Engine engine = getWrappedEngine(); LongMap items2 = checkClosed(items); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ items2.remove(recid); engine.delete(recid,serializer); }finally { lock.unlock(); } } @Override public boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) { Engine engine = getWrappedEngine(); LongMap items2 = checkClosed(items); ReferenceQueue q = checkClosed(queue); final Lock lock = locks[Store.lockPos(recid)]; lock.lock(); try{ CacheItem item = items2.get(recid); Object oldValue = item==null? null: item.get() ; if(item!=null && item.getRecid() == recid && (oldValue == expectedOldValue || (oldValue!=null && oldValue.equals(expectedOldValue)))){ //found matching entry in cache, so just update and return true items2.put(recid,useWeakRef? new CacheWeakItem(newValue, q, recid) : new CacheSoftItem(newValue, q, recid)); engine.update(recid, newValue, serializer); return true; }else{ boolean ret = engine.compareAndSwap(recid, expectedOldValue, newValue, serializer); if(ret){ items2.put(recid,useWeakRef? new CacheWeakItem(newValue, q, recid) : new CacheSoftItem(newValue, q, recid)); } return ret; } }finally { lock.unlock(); } } @Override public void close() { super.close(); items = null; queue = null; if (queueThread != null) { queueThread.interrupt(); queueThread = null; } } @Override public void rollback() { items.clear(); super.rollback(); } @Override public void clearCache() { items.clear(); super.clearCache(); } } /** * Cache created objects using hard reference. * It checks free memory every N operations (1024*10). If free memory is bellow 75% it clears the cache * * @author Jan Kotek */ public static class HardRef extends LRU { final static int CHECK_EVERY_N = 10000; int counter = 0; public HardRef(Engine engine, int initialCapacity) { super(engine, new LongConcurrentHashMap(initialCapacity)); } @Override public A get(long recid, Serializer serializer) { checkFreeMem(); return super.get(recid, serializer); } private void checkFreeMem() { if((counter++)%CHECK_EVERY_N==0 ){ Runtime r = Runtime.getRuntime(); long max = r.maxMemory(); if(max == Long.MAX_VALUE) return; double free = r.freeMemory(); double total = r.totalMemory(); //We believe that free refers to total not max. //Increasing heap size to max would increase to max free = free + (max-total); //TODO logging // if(CC.LOG_TRACE) // Utils.LOG.fine("DBCache: freemem = " +free + " = "+(free/max)+"%"); if(free<1e7 || free*4 void update(long recid, A value, Serializer serializer) { checkFreeMem(); super.update(recid, value, serializer); } @Override public void delete(long recid, Serializer serializer){ checkFreeMem(); super.delete(recid,serializer); } @Override public boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) { checkFreeMem(); return super.compareAndSwap(recid, expectedOldValue, newValue, serializer); } } }