/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.kernel.concurrent;
import com.liferay.portal.kernel.util.StringBundler;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author Shuyang Zhou
*/
public class ConcurrentLFUCache<K, V> {
public ConcurrentLFUCache(int maxSize) {
this(maxSize, 0.75F);
}
public ConcurrentLFUCache(int maxSize, float loadFactor) {
if ((maxSize <= 0) || (loadFactor <= 0) || (loadFactor >= 1)) {
throw new IllegalArgumentException();
}
_maxSize = maxSize;
_expectedSize = (int)(maxSize * loadFactor);
if (_expectedSize == 0) {
throw new IllegalArgumentException(
"maxSize and loadFactor are too small");
}
_readLock = _readWriteLock.readLock();
_writeLock = _readWriteLock.writeLock();
}
public void clear() {
_writeLock.lock();
try {
_cache.clear();
}
finally {
_writeLock.unlock();
}
}
public long evictCount() {
return _evictCount.get();
}
public int expectedSize() {
return _expectedSize;
}
public V get(K key) {
_readLock.lock();
try {
ValueWrapper valueWrapper = _cache.get(key);
if (valueWrapper != null) {
valueWrapper._hitCount.getAndIncrement();
_hitCount.getAndIncrement();
return valueWrapper._value;
}
}
finally {
_readLock.unlock();
}
_missCount.getAndIncrement();
return null;
}
public long hitCount() {
return _hitCount.get();
}
public int maxSize() {
return _maxSize;
}
public long missCount() {
return _missCount.get();
}
public void put(K key, V value) {
if (key == null) {
throw new NullPointerException("Key is null");
}
ValueWrapper valueWrapper = new ValueWrapper(value);
_writeLock.lock();
try {
if (!_cache.containsKey(key) && (_cache.size() >= _maxSize)) {
_cleanUp();
}
_cache.put(key, valueWrapper);
}
finally {
_writeLock.unlock();
}
_putCount.getAndIncrement();
}
public long putCount() {
return _putCount.get();
}
public int size() {
_readLock.lock();
try {
return _cache.size();
}
finally {
_readLock.unlock();
}
}
@Override
public String toString() {
StringBundler sb = new StringBundler(15);
sb.append("{evictCount=");
sb.append(_evictCount.get());
sb.append(", expectedSize=");
sb.append(_expectedSize);
sb.append(", hitCount=");
sb.append(_hitCount.get());
sb.append(", maxSize=");
sb.append(_maxSize);
sb.append(", missCount=");
sb.append(_missCount.get());
sb.append(", putCount=");
sb.append(_putCount.get());
sb.append(", size=");
sb.append(size());
sb.append("}");
return sb.toString();
}
protected void onRemove(K key, V value) {
}
private void _cleanUp() {
List<Entry<K, ValueWrapper>> valueWrappers = new ArrayList<>(
_cache.entrySet());
Collections.sort(valueWrappers, _entryComparator);
int cleanUpSize = _cache.size() - _expectedSize;
_evictCount.getAndAdd(cleanUpSize);
Iterator<Entry<K, ValueWrapper>> itr = valueWrappers.iterator();
while ((cleanUpSize-- > 0) && itr.hasNext()) {
Entry<K, ValueWrapper> entry = itr.next();
K key = entry.getKey();
V value = entry.getValue()._value;
_cache.remove(key);
onRemove(key, value);
itr.remove();
}
}
private final Map<K, ValueWrapper> _cache = new HashMap<>();
private final EntryComparator _entryComparator = new EntryComparator();
private final AtomicLong _evictCount = new AtomicLong();
private final int _expectedSize;
private final AtomicLong _hitCount = new AtomicLong();
private final int _maxSize;
private final AtomicLong _missCount = new AtomicLong();
private final AtomicLong _putCount = new AtomicLong();
private final Lock _readLock;
private final ReentrantReadWriteLock _readWriteLock =
new ReentrantReadWriteLock();
private final Lock _writeLock;
private class EntryComparator
implements Comparator<Entry<K, ValueWrapper>> {
@Override
public int compare(
Entry<K, ValueWrapper> entry1, Entry<K, ValueWrapper> entry2) {
ValueWrapper valueWrapper1 = entry1.getValue();
ValueWrapper valueWrapper2 = entry2.getValue();
long hitCount1 = valueWrapper1._hitCount.get();
long hitCount2 = valueWrapper2._hitCount.get();
return (int)(hitCount1 - hitCount2);
}
}
private class ValueWrapper {
public ValueWrapper(V v) {
_value = v;
}
private final AtomicLong _hitCount = new AtomicLong();
private final V _value;
}
}