Freemarker source code analysis cache.CacheStorage and its subclasses

2021SC@SDUSC

cache.CacheStorage and its subclasses

Overview map

1.CacheStorage interface

code

public interface CacheStorage {
    public Object get(Object key);
    public void put(Object key, Object value);
    public void remove(Object key);
    public void clear();
}

Function: Cache storage abstracts the storage aspect of cache - associate object s with keys, and retrieve and delete them through keys. It is actually a sub interface of java.util.Map.

2.CacheStorageWithGetSize interface

code

public interface CacheStorageWithGetSize extends CacheStorage {
    int getSize();
}

Function: the cache store with getSize() method is used to return the number of current cache entries.

3. Concurrent cache storage interface

code

public interface ConcurrentCacheStorage extends CacheStorage {
    public boolean isConcurrent();
}

Function: an optional cache storage interface, which knows whether it can be accessed concurrently without synchronization.

4.NullCacheStorage class

code

public class NullCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
    
    public static final NullCacheStorage INSTANCE = new NullCacheStorage();

    @Override
    public boolean isConcurrent() {
        return true;
    }
    
    @Override
    public Object get(Object key) {
        return null;
    }

    @Override
    public void put(Object key, Object value) {
        // do nothing
    }

    @Override
    public void remove(Object key) {
        // do nothing
    }
    
    @Override
    public void clear() {
        // do nothing
    }

    public int getSize() {
        return 0;
    }
    
}

Function: a cache memory that does not store anything. If you don't need caching, you can use this.

5.SoftCacheStorage class

code

public class SoftCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
    private static final Method atomicRemove = getAtomicRemoveMethod();
    
    private final ReferenceQueue queue = new ReferenceQueue();
    private final Map map;
    private final boolean concurrent;
    
    public SoftCacheStorage() {
        this(new ConcurrentHashMap());
    }
    
    @Override
    public boolean isConcurrent() {
        return concurrent;
    }
    
    public SoftCacheStorage(Map backingMap) {
        map = backingMap;
        this.concurrent = map instanceof ConcurrentMap;
    }
    
    @Override
    public Object get(Object key) {
        processQueue();
        Reference ref = (Reference) map.get(key);
        return ref == null ? null : ref.get();
    }

    @Override
    public void put(Object key, Object value) {
        processQueue();
        map.put(key, new SoftValueReference(key, value, queue));
    }

    @Override
    public void remove(Object key) {
        processQueue();
        map.remove(key);
    }

    @Override
    public void clear() {
        map.clear();
        processQueue();
    }
    
    @Override
    public int getSize() {
        processQueue();
        return map.size();
    }

    private void processQueue() {
        for (; ; ) {
            SoftValueReference ref = (SoftValueReference) queue.poll();
            if (ref == null) {
                return;
            }
            Object key = ref.getKey();
            if (concurrent) {
                try {
                    atomicRemove.invoke(map, new Object[] { key, ref });
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new UndeclaredThrowableException(e);
                }
            } else if (map.get(key) == ref) {
                map.remove(key);
            }
        }
    }

    private static final class SoftValueReference extends SoftReference {
        private final Object key;

        SoftValueReference(Object key, Object value, ReferenceQueue queue) {
            super(value, queue);
            this.key = key;
        }

        Object getKey() {
            return key;
        }
    }
    
    private static Method getAtomicRemoveMethod() {
        try {
            return Class.forName("java.util.concurrent.ConcurrentMap").getMethod("remove", new Class[] { Object.class, Object.class });
        } catch (ClassNotFoundException e) {
            return null;
        } catch (NoSuchMethodException e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

Function: soft cache storage is a kind of cache storage that uses the SoftReference object to save the objects passed to it, so it allows the garbage collector to clear the cache when it determines that it wants to free memory. This class has the same level of thread safety as its underlying mapping. The parameterless constructor uses thread safe mapping from 2.3.24 or Java 5 or above.

6.StrongCacheStorage class

code

public class StrongCacheStorage implements ConcurrentCacheStorage, CacheStorageWithGetSize {
    
    private final Map map = new ConcurrentHashMap();

    @Override
    public boolean isConcurrent() {
        return true;
    }
    
    @Override
    public Object get(Object key) {
        return map.get(key);
    }

    @Override
    public void put(Object key, Object value) {
        map.put(key, value);
    }

    @Override
    public void remove(Object key) {
        map.remove(key);
    }
    
    @Override
    public int getSize() {
        return map.size();
    }
    
    @Override
    public void clear() {
        map.clear();
    }
}

Function: strong cache storage is a cache storage that simply encapsulates Map. It holds strong references to all objects passed to it, thus preventing the cache from being cleared during garbage collection.

7.MruCacheStorage class

code

public class MruCacheStorage implements CacheStorageWithGetSize {
    private final MruEntry strongHead = new MruEntry();
    private final MruEntry softHead = new MruEntry();
    {
        softHead.linkAfter(strongHead);
    }
    private final Map map = new HashMap();
    private final ReferenceQueue refQueue = new ReferenceQueue();
    private final int strongSizeLimit;
    private final int softSizeLimit;
    private int strongSize = 0;
    private int softSize = 0;
    
    public MruCacheStorage(int strongSizeLimit, int softSizeLimit) {
        if (strongSizeLimit < 0) throw new IllegalArgumentException("strongSizeLimit < 0");
        if (softSizeLimit < 0) throw new IllegalArgumentException("softSizeLimit < 0");
        this.strongSizeLimit = strongSizeLimit;
        this.softSizeLimit = softSizeLimit;
    }
    
    @Override
    public Object get(Object key) {
        removeClearedReferences();
        MruEntry entry = (MruEntry) map.get(key);
        if (entry == null) {
            return null;
        }
        relinkEntryAfterStrongHead(entry, null);
        Object value = entry.getValue();
        if (value instanceof MruReference) {
            // This can only happen with strongSizeLimit == 0
            return ((MruReference) value).get();
        }
        return value;
    }

    @Override
    public void put(Object key, Object value) {
        removeClearedReferences();
        MruEntry entry = (MruEntry) map.get(key);
        if (entry == null) {
            entry = new MruEntry(key, value);
            map.put(key, entry);
            linkAfterStrongHead(entry);
        } else {
            relinkEntryAfterStrongHead(entry, value);
        }
        
    }

    @Override
    public void remove(Object key) {
        removeClearedReferences();
        removeInternal(key);
    }

    private void removeInternal(Object key) {
        MruEntry entry = (MruEntry) map.remove(key);
        if (entry != null) {
            unlinkEntryAndInspectIfSoft(entry);
        }
    }

    @Override
    public void clear() {
        strongHead.makeHead();
        softHead.linkAfter(strongHead);
        map.clear();
        strongSize = softSize = 0;
        // Quick refQueue processing
        while (refQueue.poll() != null);
    }

    private void relinkEntryAfterStrongHead(MruEntry entry, Object newValue) {
        if (unlinkEntryAndInspectIfSoft(entry) && newValue == null) {
            // Turn soft reference into strong reference, unless is was cleared
            MruReference mref = (MruReference) entry.getValue();
            Object strongValue = mref.get();
            if (strongValue != null) {
                entry.setValue(strongValue);
                linkAfterStrongHead(entry);
            } else {
                map.remove(mref.getKey());
            }
        } else {
            if (newValue != null) {
                entry.setValue(newValue);
            }
            linkAfterStrongHead(entry);
        }
    }

    private void linkAfterStrongHead(MruEntry entry) {
        entry.linkAfter(strongHead);
        if (strongSize == strongSizeLimit) {
            // softHead.previous is LRU strong entry
            MruEntry lruStrong = softHead.getPrevious();
            // Attila: This is equaivalent to strongSizeLimit != 0
            // DD: But entry.linkAfter(strongHead) was just executed above, so
            //     lruStrong != strongHead is true even if strongSizeLimit == 0.
            if (lruStrong != strongHead) {
                lruStrong.unlink();
                if (softSizeLimit > 0) {
                    lruStrong.linkAfter(softHead);
                    lruStrong.setValue(new MruReference(lruStrong, refQueue));
                    if (softSize == softSizeLimit) {
                        // List is circular, so strongHead.previous is LRU soft entry
                        MruEntry lruSoft = strongHead.getPrevious();
                        lruSoft.unlink();
                        map.remove(lruSoft.getKey());
                    } else {
                        ++softSize;
                    }
                } else {
                    map.remove(lruStrong.getKey());
                }
            }
        } else {
            ++strongSize;
        }
    }

    private boolean unlinkEntryAndInspectIfSoft(MruEntry entry) {
        entry.unlink();
        if (entry.getValue() instanceof MruReference) {
            --softSize;
            return true;
        } else {
            --strongSize;
            return false;
        }
    }
    
    private void removeClearedReferences() {
        for (; ; ) {
            MruReference ref = (MruReference) refQueue.poll();
            if (ref == null) {
                break;
            }
            removeInternal(ref.getKey());
        }
    }
    
    public int getStrongSizeLimit() {
        return strongSizeLimit;
    }

    public int getSoftSizeLimit() {
        return softSizeLimit;
    }

    public int getStrongSize() {
        return strongSize;
    }

    public int getSoftSize() {
        removeClearedReferences();
        return softSize;
    }
    
    @Override
    public int getSize() {
        return getSoftSize() + getStrongSize();
    }

    private static final class MruEntry {
        private MruEntry prev;
        private MruEntry next;
        private final Object key;
        private Object value;
        
        MruEntry() {
            makeHead();
            key = value = null;
        }
        
        MruEntry(Object key, Object value) {
            this.key = key;
            this.value = value;
        }
        
        Object getKey() {
            return key;
        }
        
        Object getValue() {
            return value;
        }
        
        void setValue(Object value) {
            this.value = value;
        }

        MruEntry getPrevious() {
            return prev;
        }
        
        void linkAfter(MruEntry entry) {
            next = entry.next;
            entry.next = this;
            prev = entry;
            next.prev = this;
        }
        
        void unlink() {
            next.prev = prev;
            prev.next = next;
            prev = null;
            next = null;
        }
        
        void makeHead() {
            prev = next = this;
        }
    }
    
    private static class MruReference extends SoftReference {
        private final Object key;
        
        MruReference(MruEntry entry, ReferenceQueue queue) {
            super(entry.getValue(), queue);
            this.key = entry.getKey();
        }
        
        Object getKey() {
            return key;
        }
    }
    
    
}

Function: realize the cache storage of two-level recently used cache. In the first layer, the item is strongly referenced to the specified maximum value. When the maximum value is exceeded, the least recently used items are moved to the second level cache, where they are soft referenced until another second level cache, where they are soft referenced until another recently used item is completely discarded. This cache storage is a generalization of StrongCacheStorage and SoftCacheStorage - both effects can be achieved by setting a positive integer with a maximum value of zero and the other maximum value of maximum.

Note: Freemarker code comes from FreeMarker Chinese official reference manual

Novice code analysis, if there are errors in the article, please point out

Tags: Java FreeMarker

Posted on Sun, 24 Oct 2021 09:05:35 -0400 by shawngibson