Coverage Report - org.apache.myfaces.shared.util.ConcurrentLRUCache
 
Classes in this File Line Coverage Branch Coverage Complexity
ConcurrentLRUCache
19%
42/211
11%
12/104
2.85
ConcurrentLRUCache$1
0%
0/3
N/A
2.85
ConcurrentLRUCache$CacheEntry
43%
7/16
0%
0/4
2.85
ConcurrentLRUCache$CleanupThread
0%
0/28
0%
0/6
2.85
ConcurrentLRUCache$EvictionListener
N/A
N/A
2.85
ConcurrentLRUCache$PQueue
0%
0/16
0%
0/8
2.85
ConcurrentLRUCache$Stats
33%
7/21
N/A
2.85
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *   http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.myfaces.shared.util;
 20  
 
 21  
 import java.lang.ref.WeakReference;
 22  
 import java.util.Arrays;
 23  
 import java.util.Collections;
 24  
 import java.util.LinkedHashMap;
 25  
 import java.util.Map;
 26  
 import java.util.TreeSet;
 27  
 import java.util.concurrent.ConcurrentHashMap;
 28  
 import java.util.concurrent.atomic.AtomicInteger;
 29  
 import java.util.concurrent.atomic.AtomicLong;
 30  
 import java.util.concurrent.locks.ReentrantLock;
 31  
 
 32  
 /**
 33  
  * A LRU cache implementation based upon ConcurrentHashMap and other techniques to reduce
 34  
  * contention and synchronization overhead to utilize multiple CPU cores more effectively.
 35  
  * <p/>
 36  
  * Note that the implementation does not follow a true LRU (least-recently-used) eviction
 37  
  * strategy. Instead it strives to remove least recently used items but when the initial
 38  
  * cleanup does not remove enough items to reach the 'acceptableWaterMark' limit, it can
 39  
  * remove more items forcefully regardless of access order.
 40  
  *
 41  
  *
 42  
  * @since solr 1.4
 43  
  * @see org.apache.solr.util.ConcurrentLRUCache
 44  
  */
 45  0
 public class ConcurrentLRUCache<K, V>
 46  
 {
 47  
     //private static Logger log = Logger.getLogger(ConcurrentLRUCache.class
 48  
     //        .getName());
 49  
 
 50  
     private final ConcurrentHashMap<Object, CacheEntry<K, V>> map;
 51  
     private final int upperWaterMark;
 52  
     private final int lowerWaterMark;
 53  8
     private final ReentrantLock markAndSweepLock = new ReentrantLock(true);
 54  8
     private boolean isCleaning = false; // not volatile... piggybacked on other volatile vars
 55  
     private final boolean newThreadForCleanup;
 56  8
     private volatile boolean islive = true;
 57  8
     private final Stats stats = new Stats();
 58  
     private final int acceptableWaterMark;
 59  8
     private long oldestEntry = 0; // not volatile, only accessed in the cleaning method
 60  
     private final EvictionListener<K, V> evictionListener;
 61  
     private CleanupThread cleanupThread;
 62  
 
 63  
     public ConcurrentLRUCache(int upperWaterMark, final int lowerWaterMark,
 64  
             int acceptableWatermark, int initialSize, boolean runCleanupThread,
 65  
             boolean runNewThreadForCleanup,
 66  
             EvictionListener<K, V> evictionListener)
 67  8
     {
 68  8
         if (upperWaterMark < 1)
 69  
         {
 70  0
             throw new IllegalArgumentException("upperWaterMark must be > 0");
 71  
         }
 72  8
         if (lowerWaterMark >= upperWaterMark)
 73  
         {
 74  0
             throw new IllegalArgumentException(
 75  
                     "lowerWaterMark must be  < upperWaterMark");
 76  
         }
 77  8
         map = new ConcurrentHashMap<Object, CacheEntry<K, V>>(initialSize);
 78  8
         newThreadForCleanup = runNewThreadForCleanup;
 79  8
         this.upperWaterMark = upperWaterMark;
 80  8
         this.lowerWaterMark = lowerWaterMark;
 81  8
         this.acceptableWaterMark = acceptableWatermark;
 82  8
         this.evictionListener = evictionListener;
 83  8
         if (runCleanupThread)
 84  
         {
 85  0
             cleanupThread = new CleanupThread(this);
 86  0
             cleanupThread.start();
 87  
         }
 88  8
     }
 89  
 
 90  
     public ConcurrentLRUCache(int size, int lowerWatermark)
 91  
     {
 92  8
         this(size, lowerWatermark, (int) Math
 93  
                 .floor((lowerWatermark + size) / 2), (int) Math
 94  
                 .ceil(0.75 * size), false, false, null);
 95  8
     }
 96  
 
 97  
     public void setAlive(boolean live)
 98  
     {
 99  0
         islive = live;
 100  0
     }
 101  
 
 102  
     public V get(K key)
 103  
     {
 104  13
         CacheEntry<K, V> e = map.get(key);
 105  13
         if (e == null)
 106  
         {
 107  13
             if (islive)
 108  
             {
 109  13
                 stats.missCounter.incrementAndGet();
 110  
             }
 111  13
             return null;
 112  
         }
 113  0
         if (islive)
 114  
         {
 115  0
             e.lastAccessed = stats.accessCounter.incrementAndGet();
 116  
         }
 117  0
         return e.value;
 118  
     }
 119  
 
 120  
     public V remove(K key)
 121  
     {
 122  0
         CacheEntry<K, V> cacheEntry = map.remove(key);
 123  0
         if (cacheEntry != null)
 124  
         {
 125  0
             stats.size.decrementAndGet();
 126  0
             return cacheEntry.value;
 127  
         }
 128  0
         return null;
 129  
     }
 130  
 
 131  
     public V put(K key, V val)
 132  
     {
 133  13
         if (val == null)
 134  
         {
 135  0
             return null;
 136  
         }
 137  13
         CacheEntry<K, V> e = new CacheEntry<K, V>(key, val,
 138  
                 stats.accessCounter.incrementAndGet());
 139  13
         CacheEntry<K, V> oldCacheEntry = map.put(key, e);
 140  
         int currentSize;
 141  13
         if (oldCacheEntry == null)
 142  
         {
 143  13
             currentSize = stats.size.incrementAndGet();
 144  
         }
 145  
         else
 146  
         {
 147  0
             currentSize = stats.size.get();
 148  
         }
 149  13
         if (islive)
 150  
         {
 151  13
             stats.putCounter.incrementAndGet();
 152  
         }
 153  
         else
 154  
         {
 155  0
             stats.nonLivePutCounter.incrementAndGet();
 156  
         }
 157  
 
 158  
         // Check if we need to clear out old entries from the cache.
 159  
         // isCleaning variable is checked instead of markAndSweepLock.isLocked()
 160  
         // for performance because every put invokation will check until
 161  
         // the size is back to an acceptable level.
 162  
         //
 163  
         // There is a race between the check and the call to markAndSweep, but
 164  
         // it's unimportant because markAndSweep actually aquires the lock or returns if it can't.
 165  
         //
 166  
         // Thread safety note: isCleaning read is piggybacked (comes after) other volatile reads
 167  
         // in this method.
 168  13
         if (currentSize > upperWaterMark && !isCleaning)
 169  
         {
 170  0
             if (newThreadForCleanup)
 171  
             {
 172  0
                 new Thread()
 173  0
                 {
 174  
                     @Override
 175  
                     public void run()
 176  
                     {
 177  0
                         markAndSweep();
 178  0
                     }
 179  
                 }.start();
 180  
             }
 181  0
             else if (cleanupThread != null)
 182  
             {
 183  0
                 cleanupThread.wakeThread();
 184  
             }
 185  
             else
 186  
             {
 187  0
                 markAndSweep();
 188  
             }
 189  
         }
 190  13
         return oldCacheEntry == null ? null : oldCacheEntry.value;
 191  
     }
 192  
 
 193  
     /**
 194  
      * Removes items from the cache to bring the size down
 195  
      * to an acceptable value ('acceptableWaterMark').
 196  
      * <p/>
 197  
      * It is done in two stages. In the first stage, least recently used items are evicted.
 198  
      * If, after the first stage, the cache size is still greater than 'acceptableSize'
 199  
      * config parameter, the second stage takes over.
 200  
      * <p/>
 201  
      * The second stage is more intensive and tries to bring down the cache size
 202  
      * to the 'lowerWaterMark' config parameter.
 203  
      */
 204  
     private void markAndSweep()
 205  
     {
 206  
         // if we want to keep at least 1000 entries, then timestamps of
 207  
         // current through current-1000 are guaranteed not to be the oldest (but that does
 208  
         // not mean there are 1000 entries in that group... it's acutally anywhere between
 209  
         // 1 and 1000).
 210  
         // Also, if we want to remove 500 entries, then
 211  
         // oldestEntry through oldestEntry+500 are guaranteed to be
 212  
         // removed (however many there are there).
 213  
 
 214  0
         if (!markAndSweepLock.tryLock())
 215  
         {
 216  0
             return;
 217  
         }
 218  
         try
 219  
         {
 220  0
             long oldestEntry = this.oldestEntry;
 221  0
             isCleaning = true;
 222  0
             this.oldestEntry = oldestEntry; // volatile write to make isCleaning visible
 223  
 
 224  0
             long timeCurrent = stats.accessCounter.get();
 225  0
             int sz = stats.size.get();
 226  
 
 227  0
             int numRemoved = 0;
 228  0
             int numKept = 0;
 229  0
             long newestEntry = timeCurrent;
 230  0
             long newNewestEntry = -1;
 231  0
             long newOldestEntry = Long.MAX_VALUE;
 232  
 
 233  0
             int wantToKeep = lowerWaterMark;
 234  0
             int wantToRemove = sz - lowerWaterMark;
 235  
 
 236  
             @SuppressWarnings("unchecked")
 237  
             // generic array's are anoying
 238  0
             CacheEntry<K, V>[] eset = new CacheEntry[sz];
 239  0
             int eSize = 0;
 240  
 
 241  
             // System.out.println("newestEntry="+newestEntry + " oldestEntry="+oldestEntry);
 242  
             // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + 
 243  
             //    " esetSz="+ eSize + " sz-numRemoved=" + (sz-numRemoved));
 244  
 
 245  0
             for (CacheEntry<K, V> ce : map.values())
 246  
             {
 247  
                 // set lastAccessedCopy to avoid more volatile reads
 248  0
                 ce.lastAccessedCopy = ce.lastAccessed;
 249  0
                 long thisEntry = ce.lastAccessedCopy;
 250  
 
 251  
                 // since the wantToKeep group is likely to be bigger than wantToRemove, check it first
 252  0
                 if (thisEntry > newestEntry - wantToKeep)
 253  
                 {
 254  
                     // this entry is guaranteed not to be in the bottom
 255  
                     // group, so do nothing.
 256  0
                     numKept++;
 257  0
                     newOldestEntry = Math.min(thisEntry, newOldestEntry);
 258  
                 }
 259  0
                 else if (thisEntry < oldestEntry + wantToRemove)
 260  
                 { // entry in bottom group?
 261  
                     // this entry is guaranteed to be in the bottom group
 262  
                     // so immediately remove it from the map.
 263  0
                     evictEntry(ce.key);
 264  0
                     numRemoved++;
 265  
                 }
 266  
                 else
 267  
                 {
 268  
                     // This entry *could* be in the bottom group.
 269  
                     // Collect these entries to avoid another full pass... this is wasted
 270  
                     // effort if enough entries are normally removed in this first pass.
 271  
                     // An alternate impl could make a full second pass.
 272  0
                     if (eSize < eset.length - 1)
 273  
                     {
 274  0
                         eset[eSize++] = ce;
 275  0
                         newNewestEntry = Math.max(thisEntry, newNewestEntry);
 276  0
                         newOldestEntry = Math.min(thisEntry, newOldestEntry);
 277  
                     }
 278  
                 }
 279  0
             }
 280  
 
 281  
             // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + 
 282  
             //    " esetSz="+ eSize + " sz-numRemoved=" + (sz-numRemoved));
 283  
             // TODO: allow this to be customized in the constructor?
 284  0
             int numPasses = 1; // maximum number of linear passes over the data
 285  
 
 286  
             // if we didn't remove enough entries, then make more passes
 287  
             // over the values we collected, with updated min and max values.
 288  0
             while (sz - numRemoved > acceptableWaterMark && --numPasses >= 0)
 289  
             {
 290  
 
 291  0
                 oldestEntry = newOldestEntry == Long.MAX_VALUE ? oldestEntry
 292  
                         : newOldestEntry;
 293  0
                 newOldestEntry = Long.MAX_VALUE;
 294  0
                 newestEntry = newNewestEntry;
 295  0
                 newNewestEntry = -1;
 296  0
                 wantToKeep = lowerWaterMark - numKept;
 297  0
                 wantToRemove = sz - lowerWaterMark - numRemoved;
 298  
 
 299  
                 // iterate backward to make it easy to remove items.
 300  0
                 for (int i = eSize - 1; i >= 0; i--)
 301  
                 {
 302  0
                     CacheEntry<K, V> ce = eset[i];
 303  0
                     long thisEntry = ce.lastAccessedCopy;
 304  
 
 305  0
                     if (thisEntry > newestEntry - wantToKeep)
 306  
                     {
 307  
                         // this entry is guaranteed not to be in the bottom
 308  
                         // group, so do nothing but remove it from the eset.
 309  0
                         numKept++;
 310  
                         // remove the entry by moving the last element to it's position
 311  0
                         eset[i] = eset[eSize - 1];
 312  0
                         eSize--;
 313  
 
 314  0
                         newOldestEntry = Math.min(thisEntry, newOldestEntry);
 315  
 
 316  
                     }
 317  0
                     else if (thisEntry < oldestEntry + wantToRemove)
 318  
                     { // entry in bottom group?
 319  
 
 320  
                         // this entry is guaranteed to be in the bottom group
 321  
                         // so immediately remove it from the map.
 322  0
                         evictEntry(ce.key);
 323  0
                         numRemoved++;
 324  
 
 325  
                         // remove the entry by moving the last element to it's position
 326  0
                         eset[i] = eset[eSize - 1];
 327  0
                         eSize--;
 328  
                     }
 329  
                     else
 330  
                     {
 331  
                         // This entry *could* be in the bottom group, so keep it in the eset,
 332  
                         // and update the stats.
 333  0
                         newNewestEntry = Math.max(thisEntry, newNewestEntry);
 334  0
                         newOldestEntry = Math.min(thisEntry, newOldestEntry);
 335  
                     }
 336  
                 }
 337  
                 // System.out.println("items removed:" + numRemoved + " numKept=" + 
 338  
                 //    numKept + " esetSz="+ eSize + " sz-numRemoved=" + (sz-numRemoved));
 339  
             }
 340  
 
 341  
             // if we still didn't remove enough entries, then make another pass while
 342  
             // inserting into a priority queue
 343  0
             if (sz - numRemoved > acceptableWaterMark)
 344  
             {
 345  
 
 346  0
                 oldestEntry = newOldestEntry == Long.MAX_VALUE ? oldestEntry
 347  
                         : newOldestEntry;
 348  0
                 newOldestEntry = Long.MAX_VALUE;
 349  0
                 newestEntry = newNewestEntry;
 350  0
                 newNewestEntry = -1;
 351  0
                 wantToKeep = lowerWaterMark - numKept;
 352  0
                 wantToRemove = sz - lowerWaterMark - numRemoved;
 353  
 
 354  0
                 PQueue<K, V> queue = new PQueue<K, V>(wantToRemove);
 355  
 
 356  0
                 for (int i = eSize - 1; i >= 0; i--)
 357  
                 {
 358  0
                     CacheEntry<K, V> ce = eset[i];
 359  0
                     long thisEntry = ce.lastAccessedCopy;
 360  
 
 361  0
                     if (thisEntry > newestEntry - wantToKeep)
 362  
                     {
 363  
                         // this entry is guaranteed not to be in the bottom
 364  
                         // group, so do nothing but remove it from the eset.
 365  0
                         numKept++;
 366  
                         // removal not necessary on last pass.
 367  
                         // eset[i] = eset[eSize-1];
 368  
                         // eSize--;
 369  
 
 370  0
                         newOldestEntry = Math.min(thisEntry, newOldestEntry);
 371  
 
 372  
                     }
 373  0
                     else if (thisEntry < oldestEntry + wantToRemove)
 374  
                     { // entry in bottom group?
 375  
                         // this entry is guaranteed to be in the bottom group
 376  
                         // so immediately remove it.
 377  0
                         evictEntry(ce.key);
 378  0
                         numRemoved++;
 379  
 
 380  
                         // removal not necessary on last pass.
 381  
                         // eset[i] = eset[eSize-1];
 382  
                         // eSize--;
 383  
                     }
 384  
                     else
 385  
                     {
 386  
                         // This entry *could* be in the bottom group.
 387  
                         // add it to the priority queue
 388  
 
 389  
                         // everything in the priority queue will be removed, so keep track of
 390  
                         // the lowest value that ever comes back out of the queue.
 391  
 
 392  
                         // first reduce the size of the priority queue to account for
 393  
                         // the number of items we have already removed while executing
 394  
                         // this loop so far.
 395  0
                         queue.myMaxSize = sz - lowerWaterMark - numRemoved;
 396  0
                         while (queue.size() > queue.myMaxSize
 397  
                                 && queue.size() > 0)
 398  
                         {
 399  0
                             CacheEntry otherEntry = (CacheEntry) queue.pop();
 400  0
                             newOldestEntry = Math
 401  
                                     .min(otherEntry.lastAccessedCopy,
 402  
                                             newOldestEntry);
 403  0
                         }
 404  0
                         if (queue.myMaxSize <= 0)
 405  
                         {
 406  0
                             break;
 407  
                         }
 408  
 
 409  0
                         Object o = queue.myInsertWithOverflow(ce);
 410  0
                         if (o != null)
 411  
                         {
 412  0
                             newOldestEntry = Math.min(
 413  
                                     ((CacheEntry) o).lastAccessedCopy,
 414  
                                     newOldestEntry);
 415  
                         }
 416  
                     }
 417  
                 }
 418  
 
 419  
                 // Now delete everything in the priority queue.
 420  
                 // avoid using pop() since order doesn't matter anymore
 421  0
                 for (CacheEntry<K, V> ce : queue.getValues())
 422  
                 {
 423  0
                     if (ce == null)
 424  
                     {
 425  0
                         continue;
 426  
                     }
 427  0
                     evictEntry(ce.key);
 428  0
                     numRemoved++;
 429  0
                 }
 430  
 
 431  
                 // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + 
 432  
                 //    " initialQueueSize="+ wantToRemove + " finalQueueSize=" + 
 433  
                 //      queue.size() + " sz-numRemoved=" + (sz-numRemoved));
 434  
             }
 435  
 
 436  0
             oldestEntry = newOldestEntry == Long.MAX_VALUE ? oldestEntry
 437  
                     : newOldestEntry;
 438  0
             this.oldestEntry = oldestEntry;
 439  
         }
 440  
         finally
 441  
         {
 442  0
             isCleaning = false; // set before markAndSweep.unlock() for visibility
 443  0
             markAndSweepLock.unlock();
 444  0
         }
 445  0
     }
 446  
 
 447  0
     private static class PQueue<K, V> extends PriorityQueue<CacheEntry<K, V>>
 448  
     {
 449  
         int myMaxSize;
 450  
         final Object[] heap;
 451  
 
 452  
         PQueue(int maxSz)
 453  
         {
 454  0
             super(maxSz);
 455  0
             heap = getHeapArray();
 456  0
             myMaxSize = maxSz;
 457  0
         }
 458  
 
 459  
         @SuppressWarnings("unchecked")
 460  
         Iterable<CacheEntry<K, V>> getValues()
 461  
         {
 462  0
             return (Iterable) Collections.unmodifiableCollection(Arrays
 463  
                     .asList(heap));
 464  
         }
 465  
 
 466  
         @Override
 467  
         protected boolean lessThan(CacheEntry a, CacheEntry b)
 468  
         {
 469  
             // reverse the parameter order so that the queue keeps the oldest items
 470  0
             return b.lastAccessedCopy < a.lastAccessedCopy;
 471  
         }
 472  
 
 473  
         // necessary because maxSize is private in base class
 474  
         @SuppressWarnings("unchecked")
 475  
         public CacheEntry<K, V> myInsertWithOverflow(CacheEntry<K, V> element)
 476  
         {
 477  0
             if (size() < myMaxSize)
 478  
             {
 479  0
                 add(element);
 480  0
                 return null;
 481  
             }
 482  0
             else if (size() > 0
 483  
                     && !lessThan(element, (CacheEntry<K, V>) heap[1]))
 484  
             {
 485  0
                 CacheEntry<K, V> ret = (CacheEntry<K, V>) heap[1];
 486  0
                 heap[1] = element;
 487  0
                 updateTop();
 488  0
                 return ret;
 489  
             }
 490  
             else
 491  
             {
 492  0
                 return element;
 493  
             }
 494  
         }
 495  
     }
 496  
 
 497  
     private void evictEntry(K key)
 498  
     {
 499  0
         CacheEntry<K, V> o = map.remove(key);
 500  0
         if (o == null)
 501  
         {
 502  0
             return;
 503  
         }
 504  0
         stats.size.decrementAndGet();
 505  0
         stats.evictionCounter.incrementAndGet();
 506  0
         if (evictionListener != null)
 507  
         {
 508  0
             evictionListener.evictedEntry(o.key, o.value);
 509  
         }
 510  0
     }
 511  
 
 512  
     /**
 513  
      * Returns 'n' number of oldest accessed entries present in this cache.
 514  
      *
 515  
      * This uses a TreeSet to collect the 'n' oldest items ordered by ascending last access time
 516  
      *  and returns a LinkedHashMap containing 'n' or less than 'n' entries.
 517  
      * @param n the number of oldest items needed
 518  
      * @return a LinkedHashMap containing 'n' or less than 'n' entries
 519  
      */
 520  
     public Map<K, V> getOldestAccessedItems(int n)
 521  
     {
 522  0
         Map<K, V> result = new LinkedHashMap<K, V>();
 523  0
         if (n <= 0)
 524  
         {
 525  0
             return result;
 526  
         }
 527  0
         TreeSet<CacheEntry<K, V>> tree = new TreeSet<CacheEntry<K, V>>();
 528  0
         markAndSweepLock.lock();
 529  
         try
 530  
         {
 531  0
             for (Map.Entry<Object, CacheEntry<K, V>> entry : map.entrySet())
 532  
             {
 533  0
                 CacheEntry<K, V> ce = entry.getValue();
 534  0
                 ce.lastAccessedCopy = ce.lastAccessed;
 535  0
                 if (tree.size() < n)
 536  
                 {
 537  0
                     tree.add(ce);
 538  
                 }
 539  
                 else
 540  
                 {
 541  0
                     if (ce.lastAccessedCopy < tree.first().lastAccessedCopy)
 542  
                     {
 543  0
                         tree.remove(tree.first());
 544  0
                         tree.add(ce);
 545  
                     }
 546  
                 }
 547  0
             }
 548  
         }
 549  
         finally
 550  
         {
 551  0
             markAndSweepLock.unlock();
 552  0
         }
 553  0
         for (CacheEntry<K, V> e : tree)
 554  
         {
 555  0
             result.put(e.key, e.value);
 556  0
         }
 557  0
         return result;
 558  
     }
 559  
 
 560  
     public Map<K, V> getLatestAccessedItems(int n)
 561  
     {
 562  0
         Map<K, V> result = new LinkedHashMap<K, V>();
 563  0
         if (n <= 0)
 564  
         {
 565  0
             return result;
 566  
         }
 567  0
         TreeSet<CacheEntry<K, V>> tree = new TreeSet<CacheEntry<K, V>>();
 568  
         // we need to grab the lock since we are changing lastAccessedCopy
 569  0
         markAndSweepLock.lock();
 570  
         try
 571  
         {
 572  0
             for (Map.Entry<Object, CacheEntry<K, V>> entry : map.entrySet())
 573  
             {
 574  0
                 CacheEntry<K, V> ce = entry.getValue();
 575  0
                 ce.lastAccessedCopy = ce.lastAccessed;
 576  0
                 if (tree.size() < n)
 577  
                 {
 578  0
                     tree.add(ce);
 579  
                 }
 580  
                 else
 581  
                 {
 582  0
                     if (ce.lastAccessedCopy > tree.last().lastAccessedCopy)
 583  
                     {
 584  0
                         tree.remove(tree.last());
 585  0
                         tree.add(ce);
 586  
                     }
 587  
                 }
 588  0
             }
 589  
         }
 590  
         finally
 591  
         {
 592  0
             markAndSweepLock.unlock();
 593  0
         }
 594  0
         for (CacheEntry<K, V> e : tree)
 595  
         {
 596  0
             result.put(e.key, e.value);
 597  0
         }
 598  0
         return result;
 599  
     }
 600  
 
 601  
     public int size()
 602  
     {
 603  0
         return stats.size.get();
 604  
     }
 605  
 
 606  
     public void clear()
 607  
     {
 608  0
         map.clear();
 609  0
     }
 610  
 
 611  
     public Map<Object, CacheEntry<K, V>> getMap()
 612  
     {
 613  0
         return map;
 614  
     }
 615  
 
 616  0
     private static class CacheEntry<K, V> implements
 617  
             Comparable<CacheEntry<K, V>>
 618  
     {
 619  
         K key;
 620  
         V value;
 621  13
         volatile long lastAccessed = 0;
 622  13
         long lastAccessedCopy = 0;
 623  
 
 624  
         public CacheEntry(K key, V value, long lastAccessed)
 625  13
         {
 626  13
             this.key = key;
 627  13
             this.value = value;
 628  13
             this.lastAccessed = lastAccessed;
 629  13
         }
 630  
 
 631  
         public void setLastAccessed(long lastAccessed)
 632  
         {
 633  0
             this.lastAccessed = lastAccessed;
 634  0
         }
 635  
 
 636  
         public int compareTo(CacheEntry<K, V> that)
 637  
         {
 638  0
             if (this.lastAccessedCopy == that.lastAccessedCopy)
 639  
             {
 640  0
                 return 0;
 641  
             }
 642  0
             return this.lastAccessedCopy < that.lastAccessedCopy ? 1 : -1;
 643  
         }
 644  
 
 645  
         @Override
 646  
         public int hashCode()
 647  
         {
 648  0
             return value.hashCode();
 649  
         }
 650  
 
 651  
         @Override
 652  
         public boolean equals(Object obj)
 653  
         {
 654  0
             return value.equals(obj);
 655  
         }
 656  
 
 657  
         @Override
 658  
         public String toString()
 659  
         {
 660  0
             return "key: " + key + " value: " + value + " lastAccessed:"
 661  
                     + lastAccessed;
 662  
         }
 663  
     }
 664  
 
 665  8
     private boolean isDestroyed = false;
 666  
 
 667  
     public void destroy()
 668  
     {
 669  
         try
 670  
         {
 671  8
             if (cleanupThread != null)
 672  
             {
 673  0
                 cleanupThread.stopThread();
 674  
             }
 675  
         }
 676  
         finally
 677  
         {
 678  8
             isDestroyed = true;
 679  8
         }
 680  8
     }
 681  
 
 682  
     public Stats getStats()
 683  
     {
 684  0
         return stats;
 685  
     }
 686  
 
 687  60
     public static class Stats
 688  
     {
 689  8
         private final AtomicLong accessCounter = new AtomicLong(0);
 690  8
         private final AtomicLong putCounter = new AtomicLong(0);
 691  8
         private final AtomicLong nonLivePutCounter = new AtomicLong(0);
 692  8
         private final AtomicLong missCounter = new AtomicLong();
 693  8
         private final AtomicInteger size = new AtomicInteger();
 694  8
         private AtomicLong evictionCounter = new AtomicLong();
 695  
 
 696  
         public long getCumulativeLookups()
 697  
         {
 698  0
             return (accessCounter.get() - putCounter.get() - nonLivePutCounter
 699  
                     .get()) + missCounter.get();
 700  
         }
 701  
 
 702  
         public long getCumulativeHits()
 703  
         {
 704  0
             return accessCounter.get() - putCounter.get()
 705  
                     - nonLivePutCounter.get();
 706  
         }
 707  
 
 708  
         public long getCumulativePuts()
 709  
         {
 710  0
             return putCounter.get();
 711  
         }
 712  
 
 713  
         public long getCumulativeEvictions()
 714  
         {
 715  0
             return evictionCounter.get();
 716  
         }
 717  
 
 718  
         public int getCurrentSize()
 719  
         {
 720  0
             return size.get();
 721  
         }
 722  
 
 723  
         public long getCumulativeNonLivePuts()
 724  
         {
 725  0
             return nonLivePutCounter.get();
 726  
         }
 727  
 
 728  
         public long getCumulativeMisses()
 729  
         {
 730  0
             return missCounter.get();
 731  
         }
 732  
 
 733  
         public void add(Stats other)
 734  
         {
 735  0
             accessCounter.addAndGet(other.accessCounter.get());
 736  0
             putCounter.addAndGet(other.putCounter.get());
 737  0
             nonLivePutCounter.addAndGet(other.nonLivePutCounter.get());
 738  0
             missCounter.addAndGet(other.missCounter.get());
 739  0
             evictionCounter.addAndGet(other.evictionCounter.get());
 740  0
             size.set(Math.max(size.get(), other.size.get()));
 741  0
         }
 742  
     }
 743  
 
 744  
     public static interface EvictionListener<K, V>
 745  
     {
 746  
         public void evictedEntry(K key, V value);
 747  
     }
 748  
 
 749  
     private static class CleanupThread extends Thread
 750  
     {
 751  
         private WeakReference<ConcurrentLRUCache> cache;
 752  
 
 753  0
         private boolean stop = false;
 754  
 
 755  
         public CleanupThread(ConcurrentLRUCache c)
 756  0
         {
 757  0
             cache = new WeakReference<ConcurrentLRUCache>(c);
 758  0
         }
 759  
 
 760  
         @Override
 761  
         public void run()
 762  
         {
 763  
             while (true)
 764  
             {
 765  0
                 synchronized (this)
 766  
                 {
 767  0
                     if (stop)
 768  
                     {
 769  0
                         break;
 770  
                     }
 771  
                     try
 772  
                     {
 773  0
                         this.wait();
 774  
                     }
 775  0
                     catch (InterruptedException e)
 776  
                     {
 777  0
                     }
 778  0
                 }
 779  0
                 if (stop)
 780  
                 {
 781  0
                     break;
 782  
                 }
 783  0
                 ConcurrentLRUCache c = cache.get();
 784  0
                 if (c == null)
 785  
                 {
 786  0
                     break;
 787  
                 }
 788  0
                 c.markAndSweep();
 789  0
             }
 790  0
         }
 791  
 
 792  
         void wakeThread()
 793  
         {
 794  0
             synchronized (this)
 795  
             {
 796  0
                 this.notify();
 797  0
             }
 798  0
         }
 799  
 
 800  
         void stopThread()
 801  
         {
 802  0
             synchronized (this)
 803  
             {
 804  0
                 stop = true;
 805  0
                 this.notify();
 806  0
             }
 807  0
         }
 808  
     }
 809  
 
 810  
     @Override
 811  
     protected void finalize() throws Throwable
 812  
     {
 813  
         try
 814  
         {
 815  8
             if (!isDestroyed)
 816  
             {
 817  
                 // This log message is useless, because it is not supposed to use
 818  
                 // thread cleanup strategy for this class.
 819  
                 //log.severe("ConcurrentLRUCache was not destroyed prior to finalize()," +
 820  
                 //        " indicates a bug -- POSSIBLE RESOURCE LEAK!!!");
 821  8
                 destroy();
 822  
             }
 823  
         }
 824  
         finally
 825  
         {
 826  8
             super.finalize();
 827  8
         }
 828  8
     }
 829  
 }