001
014
015 package com.liferay.portal.kernel.concurrent;
016
017 import com.liferay.portal.kernel.util.StringBundler;
018
019 import java.util.ArrayList;
020 import java.util.Collections;
021 import java.util.Comparator;
022 import java.util.HashMap;
023 import java.util.Iterator;
024 import java.util.List;
025 import java.util.Map.Entry;
026 import java.util.Map;
027 import java.util.concurrent.atomic.AtomicLong;
028 import java.util.concurrent.locks.Lock;
029 import java.util.concurrent.locks.ReentrantReadWriteLock;
030
031
034 public class ConcurrentLRUCache<K, V> {
035
036 public ConcurrentLRUCache(int maxSize) {
037 this(maxSize, 0.75F);
038 }
039
040 public ConcurrentLRUCache(int maxSize, float loadFactor) {
041 if ((maxSize <= 0) || (loadFactor <= 0) || (loadFactor >= 1)) {
042 throw new IllegalArgumentException();
043 }
044
045 _maxSize = maxSize;
046 _expectedSize = (int)(maxSize * loadFactor);
047
048 if (_expectedSize == 0) {
049 throw new IllegalArgumentException(
050 "maxSize and loadFactor are too small");
051 }
052
053 _readLock = _readWriteLock.readLock();
054 _writeLock = _readWriteLock.writeLock();
055 }
056
057 public void clear() {
058 _writeLock.lock();
059
060 try {
061 _cache.clear();
062 }
063 finally {
064 _writeLock.unlock();
065 }
066 }
067
068 public long evictCount() {
069 return _evictCount.get();
070 }
071
072 public V get(K key) {
073 _readLock.lock();
074
075 try {
076 ValueWrapper valueWrapper = _cache.get(key);
077
078 if (valueWrapper != null) {
079 valueWrapper._hitCount.getAndIncrement();
080
081 _hitCount.getAndIncrement();
082
083 return valueWrapper._value;
084 }
085 }
086 finally {
087 _readLock.unlock();
088 }
089
090 _missCount.getAndIncrement();
091
092 return null;
093 }
094
095 public int expectedSize() {
096 return _expectedSize;
097 }
098
099 public long hitCount() {
100 return _hitCount.get();
101 }
102
103 public int maxSize() {
104 return _maxSize;
105 }
106
107 public long missCount() {
108 return _missCount.get();
109 }
110
111 public void put(K key, V value) {
112 if (key == null) {
113 throw new NullPointerException("Key is null");
114 }
115
116 ValueWrapper valueWrapper = new ValueWrapper(value);
117
118 _writeLock.lock();
119 try {
120 if (!_cache.containsKey(key) && (_cache.size() >= _maxSize)) {
121 _cleanUp();
122 }
123
124 _cache.put(key, valueWrapper);
125 }
126 finally {
127 _writeLock.unlock();
128 }
129
130 _putCount.getAndIncrement();
131 }
132
133 public long putCount() {
134 return _putCount.get();
135 }
136
137 public int size() {
138 _readLock.lock();
139
140 try {
141 return _cache.size();
142 }
143 finally {
144 _readLock.unlock();
145 }
146 }
147
148 @Override
149 public String toString() {
150 StringBundler sb = new StringBundler();
151
152 sb.append("{evictCount=");
153 sb.append(_evictCount.get());
154 sb.append(", expectedSize=");
155 sb.append(_expectedSize);
156 sb.append(", hitCount=");
157 sb.append(_hitCount.get());
158 sb.append(", maxSize=");
159 sb.append(_maxSize);
160 sb.append(", missCount=");
161 sb.append(_missCount.get());
162 sb.append(", putCount=");
163 sb.append(_putCount.get());
164 sb.append(", size=");
165 sb.append(size());
166 sb.append("}");
167
168 return sb.toString();
169 }
170
171 private void _cleanUp() {
172 List<Entry<K, ValueWrapper>> valueWrappers =
173 new ArrayList<Entry<K, ValueWrapper>>(_cache.entrySet());
174
175 Collections.sort(valueWrappers, _entryComparator);
176
177 int cleanUpSize = _cache.size() - _expectedSize;
178
179 _evictCount.getAndAdd(cleanUpSize);
180
181 Iterator<Entry<K, ValueWrapper>> itr = valueWrappers.iterator();
182
183 while (cleanUpSize-- > 0 && itr.hasNext()) {
184 Entry<K, ValueWrapper> entry = itr.next();
185
186 _cache.remove(entry.getKey());
187
188 itr.remove();
189 }
190 }
191
192 private Map<K, ValueWrapper> _cache = new HashMap<K, ValueWrapper>();
193 private final EntryComparator _entryComparator = new EntryComparator();
194 private final AtomicLong _evictCount = new AtomicLong();
195 private final int _expectedSize;
196 private final AtomicLong _hitCount = new AtomicLong();
197 private final int _maxSize;
198 private final AtomicLong _missCount = new AtomicLong();
199 private final AtomicLong _putCount = new AtomicLong();
200 private final Lock _readLock;
201 private final ReentrantReadWriteLock _readWriteLock =
202 new ReentrantReadWriteLock();
203 private final Lock _writeLock;
204
205 private class EntryComparator
206 implements Comparator<Entry<K, ValueWrapper>> {
207
208 public int compare(
209 Entry<K, ValueWrapper> entry1, Entry<K, ValueWrapper> entry2) {
210
211 ValueWrapper valueWrapper1 = entry1.getValue();
212 ValueWrapper valueWrapper2 = entry2.getValue();
213
214 long hitCount1 = valueWrapper1._hitCount.get();
215 long hitCount2 = valueWrapper2._hitCount.get();
216
217 return (int)(hitCount1 - hitCount2);
218 }
219
220 }
221
222 private class ValueWrapper {
223
224 public ValueWrapper(V v) {
225 _value = v;
226 }
227
228 private final AtomicLong _hitCount = new AtomicLong();
229 private final V _value;
230
231 }
232
233 }