📄 concurrentreaderhashmap.java
字号:
/* * WebWork, Web Application Framework * * Distributable under Apache license. * See terms of license at opensource.org *//* File: ConcurrentReaderHashMap Written by Doug Lea. Adapted from JDK1.2 HashMap.java and Hashtable.java which carries the following copyright: * Copyright 1997 by Sun Microsystems, Inc., * 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. * All rights reserved. * * This software is the confidential and proprietary information * of Sun Microsystems, Inc. ("Confidential Information"). You * shall not disclose such Confidential Information and shall use * it only in accordance with the terms of the license agreement * you entered into with Sun. History: Date Who What 28oct1999 dl Created 14dec1999 dl jmm snapshot 19apr2000 dl use barrierLock 12jan2001 dl public release 17nov2001 dl Minor tunings 20may2002 dl BarrierLock can now be serialized. 09dec2002 dl Fix interference checks.*/package webwork.util;import java.util.Map;import java.util.AbstractMap;import java.util.AbstractSet;import java.util.AbstractCollection;import java.util.Collection;import java.util.Set;import java.util.Iterator;import java.util.Enumeration;import java.util.ConcurrentModificationException;import java.util.NoSuchElementException;import java.io.Serializable;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;/** * A version of Hashtable that supports mostly-concurrent reading, but * exclusive writing. Because reads are not limited to periods * without writes, a concurrent reader policy is weaker than a classic * reader/writer policy, but is generally faster and allows more * concurrency. This class is a good choice especially for tables that * are mainly created by one thread during the start-up phase of a * program, and from then on, are mainly read (with perhaps occasional * additions or removals) in many threads. If you also need concurrency * among writes, consider instead using ConcurrentHashMap. * <p> * * Successful retrievals using get(key) and containsKey(key) usually * run without locking. Unsuccessful ones (i.e., when the key is not * present) do involve brief synchronization (locking). Also, the * size and isEmpty methods are always synchronized. * * <p> Because retrieval operations can ordinarily overlap with * writing operations (i.e., put, remove, and their derivatives), * retrievals can only be guaranteed to return the results of the most * recently <em>completed</em> operations holding upon their * onset. Retrieval operations may or may not return results * reflecting in-progress writing operations. However, the retrieval * operations do always return consistent results -- either those * holding before any single modification or after it, but never a * nonsense result. For aggregate operations such as putAll and * clear, concurrent reads may reflect insertion or removal of only * some entries. In those rare contexts in which you use a hash table * to synchronize operations across threads (for example, to prevent * reads until after clears), you should either encase operations * in synchronized blocks, or instead use java.util.Hashtable. * * <p> * * This class also supports optional guaranteed * exclusive reads, simply by surrounding a call within a synchronized * block, as in <br> * <code>ConcurrentReaderHashMap t; ... Object v; <br> * synchronized(t) { v = t.get(k); } </code> <br> * * But this is not usually necessary in practice. For * example, it is generally inefficient to write: * * <pre> * ConcurrentReaderHashMap t; ... // Inefficient version * Object key; ... * Object value; ... * synchronized(t) { * if (!t.containsKey(key)) * t.put(key, value); * // other code if not previously present * } * else { * // other code if it was previously present * } * } *</pre> * Instead, if the values are intended to be the same in each case, just take advantage of the fact that put returns * null if the key was not previously present: * <pre> * ConcurrentReaderHashMap t; ... // Use this instead * Object key; ... * Object value; ... * Object oldValue = t.put(key, value); * if (oldValue == null) { * // other code if not previously present * } * else { * // other code if it was previously present * } *</pre> * <p> * * Iterators and Enumerations (i.e., those returned by * keySet().iterator(), entrySet().iterator(), values().iterator(), * keys(), and elements()) return elements reflecting the state of the * hash table at some point at or since the creation of the * iterator/enumeration. They will return at most one instance of * each element (via next()/nextElement()), but might or might not * reflect puts and removes that have been processed since they were * created. They do <em>not</em> throw ConcurrentModificationException. * However, these iterators are designed to be used by only one * thread at a time. Sharing an iterator across multiple threads may * lead to unpredictable results if the table is being concurrently * modified. Again, you can ensure interference-free iteration by * enclosing the iteration in a synchronized block. <p> * * This class may be used as a direct replacement for any use of * java.util.Hashtable that does not depend on readers being blocked * during updates. Like Hashtable but unlike java.util.HashMap, * this class does NOT allow <tt>null</tt> to be used as a key or * value. This class is also typically faster than ConcurrentHashMap * when there is usually only one thread updating the table, but * possibly many retrieving values from it. * <p> * * Implementation note: A slightly faster implementation of * this class will be possible once planned Java Memory Model * revisions are in place. * * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>] **/public class ConcurrentReaderHashMap extends AbstractMap implements Map, Cloneable, Serializable { /* The basic strategy is an optimistic-style scheme based on the guarantee that the hash table and its lists are always kept in a consistent enough state to be read without locking: * Read operations first proceed without locking, by traversing the apparently correct list of the apparently correct bin. If an entry is found, but not invalidated (value field null), it is returned. If not found, operations must recheck (after a memory barrier) to make sure they are using both the right list and the right table (which can change under resizes). If invalidated, reads must acquire main update lock to wait out the update, and then re-traverse. * All list additions are at the front of each bin, making it easy to check changes, and also fast to traverse. Entry next pointers are never assigned. Remove() builds new nodes when necessary to preserve this. * Remove() (also clear()) invalidates removed nodes to alert read operations that they must wait out the full modifications. */ /** A Serializable class for barrier lock **/ protected static class BarrierLock implements java.io.Serializable { } /** * Lock used only for its memory effects. **/ protected final BarrierLock barrierLock = new BarrierLock(); /** * field written to only to guarantee lock ordering. **/ protected transient Object lastWrite; /** * Force a memory synchronization that will cause * all readers to see table. Call only when already * holding main synch lock. **/ protected final void recordModification(Object x) { synchronized(barrierLock) { lastWrite = x; } } /** * Get ref to table; the reference and the cells it * accesses will be at least as fresh as from last * use of barrierLock **/ protected final Entry[] getTableForReading() { synchronized(barrierLock) { return table; } } /** * The default initial number of table slots for this table (32). * Used when not otherwise specified in constructor. **/ public static int DEFAULT_INITIAL_CAPACITY = 32; /** * The minimum capacity, used if a lower value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two. */ private static final int MINIMUM_CAPACITY = 4; /** * The maximum capacity, used if a higher value is implicitly specified * by either of the constructors with arguments. * MUST be a power of two <= 1<<30. */ private static final int MAXIMUM_CAPACITY = 1 << 30; /** * The default load factor for this table (1.0). * Used when not otherwise specified in constructor. **/ public static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * The hash table data. */ protected transient Entry[] table; /** * The total number of mappings in the hash table. */ protected transient int count; /** * The table is rehashed when its size exceeds this threshold. (The * value of this field is always (int)(capacity * loadFactor).) * * @serial */ protected int threshold; /** * The load factor for the hash table. * * @serial */ protected float loadFactor; /** * Returns the appropriate capacity (power of two) for the specified * initial capacity argument. */ private int p2capacity(int initialCapacity) { int cap = initialCapacity; // Compute the appropriate capacity int result; if (cap > MAXIMUM_CAPACITY || cap < 0) { result = MAXIMUM_CAPACITY; } else { result = MINIMUM_CAPACITY; while (result < cap) result <<= 1; } return result; } /** * Return hash code for Object x. Since we are using power-of-two * tables, it is worth the effort to improve hashcode via * the same multiplicative scheme as used in IdentityHashMap. */ private static int hash(Object x) { int h = x.hashCode(); // Multiply by 127 (quickly, via shifts), and mix in some high // bits to help guard against bunching of codes that are // consecutive or equally spaced. return ((h << 7) - h + (h >>> 9) + (h >>> 17)); } /** * Check for equality of non-null references x and y. **/ protected boolean eq(Object x, Object y) { return x == y || x.equals(y); } /** * Constructs a new, empty map with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity * The actual initial capacity is rounded to the nearest power of two. * @param loadFactor the load factor of the ConcurrentReaderHashMap * @throws IllegalArgumentException if the initial maximum number * of elements is less * than zero, or if the load factor is nonpositive. */ public ConcurrentReaderHashMap(int initialCapacity, float loadFactor) { if (loadFactor <= 0) throw new IllegalArgumentException("Illegal Load factor: "+ loadFactor); this.loadFactor = loadFactor; int cap = p2capacity(initialCapacity); table = new Entry[cap]; threshold = (int)(cap * loadFactor); } /** * Constructs a new, empty map with the specified initial * capacity and default load factor. * * @param initialCapacity the initial capacity of the * ConcurrentReaderHashMap. * @throws IllegalArgumentException if the initial maximum number * of elements is less * than zero. */ public ConcurrentReaderHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs a new, empty map with a default initial capacity * and load factor. */ public ConcurrentReaderHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } /** * Constructs a new map with the same mappings as the given map. The * map is created with a capacity of twice the number of mappings in * the given map or 16 (whichever is greater), and a default load factor. */ public ConcurrentReaderHashMap(Map t) { this(Math.max((int) (t.size() / DEFAULT_LOAD_FACTOR) + 1, 16), DEFAULT_LOAD_FACTOR); putAll(t); } /** * Returns the number of key-value mappings in this map. * * @return the number of key-value mappings in this map. */ public synchronized int size() { return count; } /** * Returns <tt>true</tt> if this map contains no key-value mappings. * * @return <tt>true</tt> if this map contains no key-value mappings. */ public synchronized boolean isEmpty() { return count == 0; } /** * Returns the value to which the specified key is mapped in this table. * * @param key a key in the table. * @return the value to which the key is mapped in this table; * <code>null</code> if the key is not mapped to any value in * this table. * @exception NullPointerException if the key is * <code>null</code>. * @see #put(Object, Object) */ public Object get(Object key) { // throw null pointer exception if key null int hash = hash(key); /* Start off at the apparently correct bin. If entry is found, we need to check after a barrier anyway. If not found, we need a barrier to check if we are actually in right bin. So either way, we encounter only one barrier unless we need to retry. And we only need to fully synchronize if there have been concurrent modifications. */ Entry[] tab = table; int index = hash & (tab.length - 1);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -