001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
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    /**
032     * @author Shuyang Zhou
033     */
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    }