001    /**
002     * Copyright (c) 2000-2010 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.concurrent.atomic.AtomicInteger;
020    import java.util.concurrent.atomic.AtomicLong;
021    import java.util.concurrent.locks.Lock;
022    import java.util.concurrent.locks.ReentrantReadWriteLock;
023    
024    /**
025     * @author Shuyang Zhou
026     */
027    public class ConcurrentLRUCache<K, V> {
028    
029            public ConcurrentLRUCache(int maxSize) {
030                    _maxSize = maxSize;
031    
032                    _readLock = _readWriteLock.readLock();
033                    _writeLock = _readWriteLock.writeLock();
034    
035                    _headEntry._nextEntry = _lastEntry;
036                    _lastEntry._previousEntry = _headEntry;
037            }
038    
039            public long evictCount() {
040                    return _evictCount.get();
041            }
042    
043            public V get(K key) {
044                    Entry<K, V> matchEntry = null;
045    
046                    boolean requiresMove = false;
047    
048                    _readLock.lock();
049    
050                    try {
051                            matchEntry = _lastEntry._previousEntry;
052    
053                            while (matchEntry != _headEntry) {
054                                    if (matchEntry._key.equals(key)) {
055                                            if (matchEntry._nextEntry != _lastEntry) {
056                                                    requiresMove = true;
057                                            }
058    
059                                            _hitCount.getAndIncrement();
060    
061                                            return matchEntry._value;
062                                    }
063    
064                                    matchEntry = matchEntry._previousEntry;
065                            }
066                    }
067                    finally {
068                            _readLock.unlock();
069    
070                            if (requiresMove) {
071                                    _writeLock.lock();
072    
073                                    try {
074                                            if (matchEntry._key != null) {
075                                                    _detachEntry(matchEntry);
076    
077                                                    _insertEntryBefore(_lastEntry, matchEntry);
078                                            }
079                                    }
080                                    finally {
081                                            _writeLock.unlock();
082                                    }
083                            }
084                    }
085    
086                    _missCount.getAndIncrement();
087    
088                    return null;
089            }
090    
091            public long hitCount() {
092                    return _hitCount.get();
093            }
094    
095            public int maxSize() {
096                    return _maxSize;
097            }
098    
099            public long missCount() {
100                    return _missCount.get();
101            }
102    
103            public void put(K key, V value) {
104                    if (key == null) {
105                            throw new NullPointerException("Key is null");
106                    }
107    
108                    _putCount.getAndIncrement();
109                    _writeLock.lock();
110    
111                    try {
112                            Entry<K, V> currentEntry = _lastEntry._previousEntry;
113    
114                            while (currentEntry != _headEntry) {
115                                    if (currentEntry._key.equals(key)) {
116                                            currentEntry._value = value;
117    
118                                            if (currentEntry._nextEntry != _lastEntry) {
119                                                    _detachEntry(currentEntry);
120                                                    _insertEntryBefore(_lastEntry, currentEntry);
121                                            }
122    
123                                            return;
124                                    }
125    
126                                    currentEntry = currentEntry._previousEntry;
127                            }
128    
129                            while (_size.get() >= _maxSize) {
130                                    _removeHeadEntry();
131                            }
132    
133                            _insertEntryBefore(_lastEntry, new Entry<K, V>(key, value));
134    
135                            _size.getAndIncrement();
136                    }
137                    finally {
138                            _writeLock.unlock();
139                    }
140            }
141    
142            public long putCount() {
143                    return _putCount.get();
144            }
145    
146            public int size() {
147                    return _size.get();
148            }
149    
150            public String toString() {
151                    StringBundler sb = new StringBundler();
152    
153                    sb.append("{evictCount=");
154                    sb.append(_evictCount);
155                    sb.append(", hitCount=");
156                    sb.append(_hitCount);
157                    sb.append(", maxSize=");
158                    sb.append(_maxSize);
159                    sb.append(", missCount=");
160                    sb.append(_missCount);
161                    sb.append(", putCount=");
162                    sb.append(_putCount);
163                    sb.append(", size=");
164                    sb.append(_size);
165                    sb.append("}");
166    
167                    return sb.toString();
168            }
169    
170            private void _detachEntry(Entry<K, V> entry) {
171                    entry._previousEntry._nextEntry = entry._nextEntry;
172                    entry._nextEntry._previousEntry = entry._previousEntry;
173                    entry._nextEntry = entry._previousEntry = null;
174            }
175    
176            private void _insertEntryBefore(
177                    Entry<K, V> referenceEntry, Entry<K, V> insertEntry) {
178    
179                    insertEntry._previousEntry = referenceEntry._previousEntry;
180                    insertEntry._nextEntry = referenceEntry;
181    
182                    referenceEntry._previousEntry._nextEntry = insertEntry;
183                    referenceEntry._previousEntry = insertEntry;
184            }
185    
186            private void _removeHeadEntry() {
187                    Entry<K, V> entry = _headEntry._nextEntry;
188    
189                    _detachEntry(entry);
190    
191                    entry._key = null;
192                    entry._value = null;
193    
194                    _size.getAndDecrement();
195                    _evictCount.getAndIncrement();
196            }
197    
198            private final AtomicLong _evictCount = new AtomicLong(0);
199            private final Entry<K, V> _headEntry = new Entry<K, V>(null, null);
200            private final AtomicLong _hitCount = new AtomicLong(0);
201            private final Entry<K, V> _lastEntry = new Entry<K, V>(null, null);
202            private final int _maxSize;
203            private final AtomicLong _missCount = new AtomicLong(0);
204            private final AtomicLong _putCount = new AtomicLong(0);
205            private final Lock _readLock;
206            private final ReentrantReadWriteLock _readWriteLock =
207                    new ReentrantReadWriteLock();
208            private final AtomicInteger _size = new AtomicInteger(0);
209            private final Lock _writeLock;
210    
211            private static class Entry<K, V> {
212    
213                    public Entry(K key, V value) {
214                            _key = key;
215                            _value = value;
216                    }
217    
218                    private K _key;
219                    private Entry<K, V> _nextEntry;
220                    private Entry<K, V> _previousEntry;
221                    private V _value;
222    
223            }
224    
225    }