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

com.kolibrifx.plovercrest.server.internal.engine.LeastRecentlyUsedCache Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010-2017, KolibriFX AS. Licensed under the Apache License, version 2.0.
 */

package com.kolibrifx.plovercrest.server.internal.engine;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;

public class LeastRecentlyUsedCache {
    private static final Logger log = Logger.getLogger(LeastRecentlyUsedCache.class);

    private final Map> cacheEntries = new ConcurrentHashMap<>();
    private final int maxSize;
    private final AtomicLong ageCounter = new AtomicLong();
    private final Object putLock = new Object();

    public LeastRecentlyUsedCache(final int maxSize) {
        this.maxSize = maxSize;
    }

    public void add(final CacheEntry entry) {
        if (!cacheEntries.containsKey(entry.getKey())) {
            // Locking here prevents the cache from becoming too large. (sigh)
            synchronized (putLock) {
                while (cacheEntries.size() >= maxSize) {
                    evictLeastRecentlyUsed();
                }
                cacheEntries.put(entry.getKey(), entry);
            }
        }
    }

    private void evictLeastRecentlyUsed() {
        // We could maintain a heap to make this faster, if the iteration turns out to be a bottleneck.
        // Note: ConcurrentHashMap makes this code thread safe, although it is possible that the same entry
        // is evicted more than once from different threads.
        CacheEntry entryToEvict = null;
        for (final CacheEntry entry : cacheEntries.values()) {
            if (entryToEvict == null || entry.getAge() < entryToEvict.getAge()) {
                entryToEvict = entry;
            }
        }
        if (entryToEvict != null && cacheEntries.remove(entryToEvict.getKey()) != null) {
            if (log.isTraceEnabled()) {
                log.trace("Evicting from cache: " + entryToEvict.getKey());
            }
            entryToEvict.invalidate(); // potentially slow
        }
    }

    public void entryClosed(final CacheEntry entry) {
        cacheEntries.remove(entry.getKey());
    }

    public int size() {
        return cacheEntries.size();
    }

    public long nextAge() {
        // If the age counter ever reaches Long.MAX_VALUE, strange eviction behavior will follow.
        // But it won't. I've done the math. :-)
        return ageCounter.incrementAndGet();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy