All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.pfl.basic.concurrent.SoftCache Maven / Gradle / Ivy

There is a newer version: 5.0.0
Show newest version
/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.pfl.basic.concurrent ;

import java.lang.ref.SoftReference;
import java.lang.ref.ReferenceQueue;

import java.util.Iterator;
import java.util.Map;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Set;
import java.util.AbstractSet;
import java.util.NoSuchElementException;


/**
 * A memory-sensitive implementation of the Map interface.
 *
 * 

A SoftCache object uses {@link java.lang.ref.SoftReference * soft references} to implement a memory-sensitive hash map. If the garbage * collector determines at a certain point in time that a value object in a * SoftCache entry is no longer strongly reachable, then it may * remove that entry in order to release the memory occupied by the value * object. All SoftCache objects are guaranteed to be completely * cleared before the virtual machine will throw an * OutOfMemoryError. Because of this automatic clearing feature, * the behavior of this class is somewhat different from that of other * Map implementations. * *

Both null values and the null key are supported. This class has the * same performance characteristics as the HashMap class, and has * the same efficiency parameters of initial capacity and load * factor. * *

Like most collection classes, this class is not synchronized. A * synchronized SoftCache may be constructed using the * Collections.synchronizedMap method. * *

In typical usage this class will be subclassed and the fill * method will be overridden. When the get method is invoked on a * key for which there is no mapping in the cache, it will in turn invoke the * fill method on that key in an attempt to construct a * corresponding value. If the fill method returns such a value * then the cache will be updated and the new value will be returned. Thus, * for example, a simple URL-content cache can be constructed as follows: * *

 *     public class URLCache extends SoftCache {
 *         protected Object fill(Object key) {
 *             return ((URL)key).getContent();
 *         }
 *     }
 * 
* *

The behavior of the SoftCache class depends in part upon * the actions of the garbage collector, so several familiar (though not * required) Map invariants do not hold for this class.

* Because entries are removed from a SoftCache in response to * dynamic advice from the garbage collector, a SoftCache may * behave as though an unknown thread is silently removing entries. In * particular, even if you synchronize on a SoftCache instance and * invoke none of its mutator methods, it is possible for the size * method to return smaller values over time, for the isEmpty * method to return false and then true, for the * containsKey method to return true and later * false for a given key, for the get method to * return a value for a given key but later return null, for the * put method to return null and the * remove method to return false for a key that * previously appeared to be in the map, and for successive examinations of the * key set, the value set, and the entry set to yield successively smaller * numbers of elements. *

* I copied this from JDK 1.4.2 to avoid introducing another sun.misc * dependency in the ORB. (Ken Cavanaugh) * * @version 1.6, 03/01/23 * @author Mark Reinhold * @since JDK1.2 * @see java.util.HashMap * @see java.lang.ref.SoftReference */ public class SoftCache extends AbstractMap implements Map { /* The basic idea of this implementation is to maintain an internal HashMap that maps keys to soft references whose referents are the keys' values; the various accessor methods dereference these soft references before returning values. Because we don't have access to the innards of the HashMap, each soft reference must contain the key that maps to it so that the processQueue method can remove keys whose values have been discarded. Thus the HashMap actually maps keys to instances of the ValueCell class, which is a simple extension of the SoftReference class. */ static private class ValueCell extends SoftReference { static private int dropped = 0; private boolean keyIsValid ; private K key; private ValueCell(K key, V value, ReferenceQueue queue) { super(value, queue); this.key = key; keyIsValid = true ; } @SuppressWarnings("unchecked") private static ValueCell create( K key, V value, ReferenceQueue queue) { if (value == null) { return null; } return new ValueCell(key, value, queue); } private static V strip(ValueCell val, boolean drop) { if (val == null) { return null; } V o = val.get(); if (drop) { val.drop(); } return o; } private boolean isValid() { return keyIsValid; } private void drop() { super.clear(); keyIsValid = false ; dropped++; } } private static final int PROCESS_QUEUE_INTERVAL = 10 ; private int processQueueCount = PROCESS_QUEUE_INTERVAL ; /* Hash table mapping keys to ValueCells */ private Map> hash; /* Reference queue for cleared ValueCells */ private ReferenceQueue queue = new ReferenceQueue(); /* Process any ValueCells that have been cleared and enqueued by the garbage collector. This method should be invoked once by each public mutator in this class. We don't invoke this method in public accessors because that can lead to surprising ConcurrentModificationExceptions. */ @SuppressWarnings("unchecked") private void processQueue() { if (--processQueueCount == 0) { processQueueCount = PROCESS_QUEUE_INTERVAL ; ValueCell vc; while ((vc = (ValueCell)queue.poll()) != null) { if (vc.isValid()) { hash.remove(vc.key); } else { ValueCell.dropped--; } } } } /* -- Constructors -- */ /** * Construct a new, empty SoftCache with the given * initial capacity and the given load factor. * * @param initialCapacity The initial capacity of the cache * * @param loadFactor A number between 0.0 and 1.0 * * @throws IllegalArgumentException If the initial capacity is less than * or equal to zero, or if the load * factor is less than zero */ public SoftCache(int initialCapacity, float loadFactor) { hash = new HashMap>(initialCapacity, loadFactor); } /** * Construct a new, empty SoftCache with the given * initial capacity and the default load factor. * * @param initialCapacity The initial capacity of the cache * * @throws IllegalArgumentException If the initial capacity is less than * or equal to zero */ public SoftCache(int initialCapacity) { hash = new HashMap>(initialCapacity); } /** * Construct a new, empty SoftCache with the default * capacity and the default load factor. */ public SoftCache() { hash = new HashMap>(); } /* -- Simple queries -- */ /** * Return the number of key-value mappings in this cache. The time * required by this operation is linear in the size of the map. */ @Override public int size() { return entrySet().size(); } /** * Return true if this cache contains no key-value mappings. */ @Override public boolean isEmpty() { return entrySet().isEmpty(); } /** * Return true if this cache contains a mapping for the * specified key. If there is no mapping for the key, this method will not * attempt to construct one by invoking the fill method. * * @param key The key whose presence in the cache is to be tested */ @Override @SuppressWarnings("unchecked") public boolean containsKey(Object key) { return ValueCell.strip(hash.get((K)key), false) != null; } /* -- Lookup and modification operations -- */ /** * Create a value object for the given key. This method is * invoked by the get method when there is no entry for * key. If this method returns a non-null value, * then the cache will be updated to map key to that value, * and that value will be returned by the get method. * *

The default implementation of this method simply returns * null for every key value. A subclass may * override this method to provide more useful behavior. * * @param key The key for which a value is to be computed * * @return A value for key, or null if one * could not be computed * @see #get */ protected V fill(Object key) { return null; } /** * Return the value to which this cache maps the specified * key. If the cache does not presently contain a value for * this key, then invoke the fill method in an attempt to * compute such a value. If that method returns a non-null * value, then update the cache and return the new value. Otherwise, * return null. * *

Note that because this method may update the cache, it is considered * a mutator and may cause ConcurrentModificationExceptions to * be thrown if invoked while an iterator is in use. * * @param key The key whose associated value, if any, is to be returned * * @see #fill */ @Override @SuppressWarnings("unchecked") public V get(Object key) { processQueue(); ValueCell vc = hash.get(key); if (vc == null) { V v = fill(key); if (v != null) { hash.put((K)key, ValueCell.create((K)key, v, queue)); return v; } } return ValueCell.strip(vc, false); } /** * Update this cache so that the given key maps to the given * value. If the cache previously contained a mapping for * key then that mapping is replaced and the old value is * returned. * * @param key The key that is to be mapped to the given * value * @param value The value to which the given key is to be * mapped * * @return The previous value to which this key was mapped, or * null if if there was no mapping for the key */ @Override public V put(K key, V value) { processQueue(); ValueCell vc = ValueCell.create(key, value, queue); return ValueCell.strip(hash.put(key, vc), true); } /** * Remove the mapping for the given key from this cache, if * present. * * @param key The key whose mapping is to be removed * * @return The value to which this key was mapped, or null if * there was no mapping for the key */ @Override public V remove(Object key) { processQueue(); return ValueCell.strip(hash.remove(key), true); } /** * Remove all mappings from this cache. */ @Override public void clear() { processQueue(); hash.clear(); } /* -- Views -- */ private static boolean valEquals(Object o1, Object o2) { return (o1 == null) ? (o2 == null) : o1.equals(o2); } /* Internal class for entries. Because it uses SoftCache.this.queue, this class cannot be static. */ private class Entry implements Map.Entry { private Map.Entry> ent; private V value; /* Strong reference to value, to prevent the GC from flushing the value while this Entry exists */ Entry(Map.Entry> ent, V value) { this.ent = ent; this.value = value; } @Override public K getKey() { return ent.getKey(); } @Override public V getValue() { return value; } @Override public V setValue(V value) { ent.setValue(ValueCell.create(ent.getKey(), value, queue)); return value ; } @Override public boolean equals(Object o) { if (! (o instanceof Map.Entry)) { return false; } @SuppressWarnings("unchecked") Map.Entry e = (Map.Entry)o; return (valEquals(ent.getKey(), e.getKey()) && valEquals(value, e.getValue())); } @Override public int hashCode() { Object k; return ((((k = getKey()) == null) ? 0 : k.hashCode()) ^ ((value == null) ? 0 : value.hashCode())); } } /* Internal class for entry sets */ private class EntrySet extends AbstractSet> { Set>> hashEntries = hash.entrySet(); @Override public Iterator> iterator() { return new Iterator>() { Iterator>> hashIterator = hashEntries.iterator(); Entry next = null; @Override public boolean hasNext() { while (hashIterator.hasNext()) { Map.Entry> ent = hashIterator.next(); ValueCell vc = ent.getValue(); V v = null; if ((vc != null) && ((v = vc.get()) == null)) { /* Value has been flushed by GC */ continue; } next = new Entry(ent, v); return true; } return false; } @Override public Map.Entry next() { if ((next == null) && !hasNext()) { throw new NoSuchElementException(); } Map.Entry e = next; next = null; return e; } @Override public void remove() { hashIterator.remove(); } }; } @Override public boolean isEmpty() { return !(iterator().hasNext()); } @Override public int size() { int j = 0; for (Iterator i = iterator(); i.hasNext(); i.next()) { j++; } return j; } public boolean remove(Map.Entry o) { processQueue(); if (Entry.class.isInstance(o)) { return hashEntries.remove(((Entry) o).ent); } else { return false; } } } private Set> entrySet = null; /** * Return a Set view of the mappings in this cache. */ @Override public Set> entrySet() { if (entrySet == null) { entrySet = new EntrySet(); } return entrySet; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy