diff options
Diffstat (limited to 'java/src/IceUtil/Cache.java')
-rw-r--r-- | java/src/IceUtil/Cache.java | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/java/src/IceUtil/Cache.java b/java/src/IceUtil/Cache.java new file mode 100644 index 00000000000..899a35892fc --- /dev/null +++ b/java/src/IceUtil/Cache.java @@ -0,0 +1,298 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +package IceUtil; + +/** + * An abstraction to efficiently maintain a cache, without holding + * a lock on the entire cache while objects are being loaded from + * their backing store. This class is useful mainly to implement + * evictors, such as used by Freeze. + * + * @see Store + * @see Freeze.Evictor + **/ + +public class Cache +{ + /** + * Initialize a cache using the specified backing store. + **/ + public Cache(Store store) + { + _store = store; + } + + /** + * Return the value stored for the given key from the cache. + * + * @param key The key for the object to look up in the cache. + * + * @return If the cache contains an entry for the key, the return value + * is the object corresponding to the key; otherwise, the return value + * is null. <code>getIfPinned</code> does not call {@link Store#load}. + * + * @see Store#load + **/ + public Object + getIfPinned(Object key) + { + synchronized(_map) + { + CacheValue val = (CacheValue)_map.get(key); + return val == null ? null : val.obj; + } + } + + /** + * Removes the entry for the given key from the cache. + * + * @param key The key for the entry to remove. + * + * @return If the cache contains an entry for the key, the + * return value is the corresponding object; otherwise, the + * return value is <code>null</code>. + **/ + public Object + unpin(Object key) + { + synchronized(_map) + { + CacheValue val = (CacheValue)_map.remove(key); + return val == null ? null : val.obj; + } + } + + /** + * Removes all entries from the cache. + **/ + public void + clear() + { + synchronized(_map) + { + _map.clear(); + } + } + + /** + * Returns the number of entries in the cache. + * + * @return The number of entries. + **/ + public int + size() + { + synchronized(_map) + { + return _map.size(); + } + } + + /** + * Adds a key-value pair to the cache. + * This version of <code>pin</code> does not call {@link Store#load} to retrieve + * an entry from backing store if an entry for the given key is not yet in the cache. This + * is useful to add a newly-created object to the cache. + * + * @param key The key for the entry. + * @param o The value for the entry. + * @return If the cache already contains an entry with the given key, the entry is + * unchanged and <code>pin(Object, Object)</code> returns the original value for the entry; otherwise, + * the entry is added and <code>pin(Object, Object)</code> returns <code>null</code>. + **/ + public Object + pin(Object key, Object o) + { + synchronized(_map) + { + CacheValue existingVal = (CacheValue)_map.put(key, new CacheValue(o)); + if(existingVal != null) + { + _map.put(key, existingVal); + return existingVal.obj; + } + else + { + return null; + } + } + } + + /** + * Returns an object from the cache. + * If no entry with the given key is in the cache, <code>pin</code> calls + * {@link Store#load} to retrieve the corresponding value (if any) from the + * backing store. + * + * @param key The key for the entry to retrieve. + * @return Returns the value for the corresponding key if the cache + * contains an entry for the key. Otherwise, <code>pin(Object)</code> calls + * {@link Store#load} and the return value is whatever is returned by + * <code>load</code>; if <code>load</code> throws an exception, that exception + * is thrown by <code>pin(Object)</code>. + **/ + public Object + pin(Object key) + { + return pinImpl(key, null); + } + + /** + * Adds a key-value pair to the cache. + * @param key The key for the entry. + * @param newObj The value for the entry. + * @return If the cache already contains an entry for the given key, + * <code>putIfAbsent</code> returns the original value for that key. + * If no entry is for the given key is in the cache, <code>putIfAbsent</code> + * calls {@link Store#load} to retrieve the corresponding entry (if any) from + * the backings store and returns the value returned by <code>load</code>. + * If the cache does not contain an entry for the given key and <code>load</code> + * does not return a value for the key, <code>putIfAbsent</code> adds the new entry + * and returns <code>null</code>. + **/ + public Object + putIfAbsent(Object key, Object newObj) + { + return pinImpl(key, newObj); + } + + static private class CacheValue + { + CacheValue() + { + } + + CacheValue(Object obj) + { + this.obj = obj; + } + + Object obj = null; + java.util.concurrent.CountDownLatch latch = null; + } + + private Object + pinImpl(Object key, Object newObj) + { + for(;;) + { + CacheValue val = null; + java.util.concurrent.CountDownLatch latch = null; + + synchronized(_map) + { + val = (CacheValue)_map.get(key); + if(val == null) + { + val = new CacheValue(); + _map.put(key, val); + } + else + { + if(val.obj != null) + { + return val.obj; + } + if(val.latch == null) + { + // + // The first queued thread creates the latch + // + val.latch = new java.util.concurrent.CountDownLatch(1); + } + latch = val.latch; + } + } + + if(latch != null) + { + try + { + latch.await(); + } + catch(InterruptedException e) + { + // Ignored + } + + // + // val could be stale now, e.g. some other thread pinned and unpinned the + // object while we were waiting. + // So start over. + // + continue; + } + else + { + Object obj; + try + { + obj = _store.load(key); + } + catch(RuntimeException e) + { + synchronized(_map) + { + _map.remove(key); + latch = val.latch; + val.latch = null; + } + if(latch != null) + { + latch.countDown(); + assert latch.getCount() == 0; + } + throw e; + } + + synchronized(_map) + { + if(obj != null) + { + val.obj = obj; + } + else + { + if(newObj == null) + { + // + // pin() did not find the object + // + + // + // The waiting threads will have to call load() to see by themselves. + // + _map.remove(key); + } + else + { + // + // putIfAbsent() inserts key/newObj + // + val.obj = newObj; + } + } + + latch = val.latch; + val.latch = null; + } + if(latch != null) + { + latch.countDown(); + assert latch.getCount() == 0; + } + return obj; + } + } + } + + private final java.util.Map<Object, CacheValue> _map = new java.util.HashMap<Object, CacheValue>(); + private final Store _store; +} |