////////////////////////////////////////////////////////////////////////////////
// ConcurrentHopscotchHashMap Class
//
////////////////////////////////////////////////////////////////////////////////
//TERMS OF USAGE
//----------------------------------------------------------------------
//
// Permission to use, copy, modify and distribute this software and
// its documentation for any purpose is hereby granted without fee,
// provided that due acknowledgments to the authors are provided and
// this permission notice appears in all copies of the software.
// The software is provided "as is". There is no warranty of any kind.
//
//Authors:
// Maurice Herlihy
// Brown University
// and
// Nir Shavit
// Tel-Aviv University
// and
// Moran Tzafrir
// Tel-Aviv University
//
// Date: Dec 2, 2008.
//
////////////////////////////////////////////////////////////////////////////////
// Programmer : Moran Tzafrir ([email protected])
//
////////////////////////////////////////////////////////////////////////////////
//package xbird.util.concurrent.map;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrentHopscotchHashMap<K, V> {
//constants ----------------------------------- static final short _NULL_DELTA_SHORT = Short.MIN_VALUE; static final int _NULL_DELTA_INT = (int) (Short.MIN_VALUE); static final long _NULL_DELTA_FIRST_LONG = (long) (Short.MIN_VALUE & 0xFFFFL); static final long _NULL_DELTA_NEXT_LONG = (long) ((Short.MIN_VALUE & 0xFFFFL) << 16); static final long _NULL_HASH_DELTA = 0x0000000080008000L; static final int _NULL_HASH = 0; static final int _SEGMENT_SHIFT = 0; //choosed by empirical tests
static final long _FIRST_MASK = 0x000000000000FFFFL; static final long _NEXT_MASK = 0x00000000FFFF0000L; static final long _HASH_MASK = 0xFFFFFFFF00000000L; static final long _NOT_NEXT_MASK = ~(_NEXT_MASK); static final long _NOT_FIRST_MASK = ~(_FIRST_MASK); static final long _NOT_HASH_MASK = ~(_HASH_MASK); static final int _NEXT_SHIFT = 16; static final int _HASH_SHIFT = 32;
static final int _RETRIES_BEFORE_LOCK = 2; static final int _MAX_DELTA_BUCKET = Short.MAX_VALUE;
static final int _CACHE_MASK = 4 - 1; static final int _NOT_CACHE_MASK = ~_CACHE_MASK; static final int _NULL_INDX = -1;
//inner classes -------------------------------
static final class Segment<K, V> extends ReentrantLock {
volatile int _timestamp; int _bucketk_mask; long[] _table_hash_delta;
Object[] _table_key_value;
//AtomicInteger _lock; int _count;
public Segment(final int initialCapacity) {
init(initialCapacity);
}
@SuppressWarnings("unchecked") static final <K, V> Segment<K, V>[] newArray(final int numSegments) { return new Segment[numSegments];
}
boolean containsKey(final K key, final int hash) {
//go over the list and look for key int start_timestamp = _timestamp; int iBucket = hash & _bucketk_mask; long data = _table_hash_delta[iBucket]; final int first_delta = (short) data; if(0 != first_delta) { if(_NULL_DELTA_SHORT == first_delta) return false;
iBucket += first_delta;
data = _table_hash_delta[iBucket];
}
do { if(hash == (data >> _HASH_SHIFT) && key.equals(_table_key_value[iBucket << 1])) return true; final int nextDelta = (int) data >> _NEXT_SHIFT; if(_NULL_DELTA_INT != nextDelta) {
iBucket += nextDelta;
data = _table_hash_delta[iBucket]; continue;
} else { final int curr_timestamp = _timestamp; if(curr_timestamp == start_timestamp) return false;
start_timestamp = curr_timestamp;
iBucket = hash & _bucketk_mask;
data = _table_hash_delta[iBucket]; final int first_delta2 = (short) data; if(0 != first_delta2) { if(_NULL_DELTA_SHORT == first_delta2) return false;
iBucket += first_delta2;
data = _table_hash_delta[iBucket];
} continue;
}
} while(true);
}
V get(final K key, final int hash) {
//go over the list and look for key int start_timestamp = 0; int iBucket = 0; long data = 0;
boolean is_need_init = true; do { if(is_need_init) {
is_need_init = false;
start_timestamp = _timestamp;
iBucket = hash & _bucketk_mask;
data = _table_hash_delta[iBucket]; final int first_delta = (short) data; if(0 != first_delta) { if(_NULL_DELTA_SHORT == first_delta) return null;
iBucket += first_delta;
data = _table_hash_delta[iBucket];
}
}
final int iRef; if(hash == (data >> _HASH_SHIFT)
&& key.equals(_table_key_value[iRef = iBucket << 1])) { final V value = (V) _table_key_value[iRef + 1]; if(_timestamp == start_timestamp) return value;
is_need_init = true; continue;
} final int nextDelta = (int) data >> _NEXT_SHIFT; if(_NULL_DELTA_INT != nextDelta) {
iBucket += nextDelta;
data = _table_hash_delta[iBucket]; continue;
} else { if(_timestamp == start_timestamp) return null;
is_need_init = true; continue;
}
} while(true);
}
V put(final K key, final int hash, final V value) {
lock(); try {
//look for key in hash-map ..................................... final int i_start_bucket = hash & _bucketk_mask; int iBucket = i_start_bucket; long data = _table_hash_delta[i_start_bucket]; final short first_delta = (short) data; if(_NULL_DELTA_SHORT != first_delta) { if(0 != first_delta) {
iBucket += first_delta;
data = _table_hash_delta[iBucket];
}
do { final int iRef; if(hash == (data >> _HASH_SHIFT)
&& key.equals(_table_key_value[iRef = (iBucket << 1)])) return (V) _table_key_value[iRef + 1]; final int next_delta = (int) data >> _NEXT_SHIFT; if(_NULL_DELTA_INT == next_delta) break; else {
iBucket += next_delta;
data = _table_hash_delta[iBucket];
}
} while(true);
}
//try to place the key in the same cache-line .................. final int i_start_cacheline = i_start_bucket & _NOT_CACHE_MASK; final int i_end_cacheline = i_start_cacheline + _CACHE_MASK; int i_free_bucket = i_start_bucket; do { long free_data = _table_hash_delta[i_free_bucket]; if(_NULL_HASH == (free_data >> _HASH_SHIFT)) {
//we found a free bucket at the cahce-line, so
//add the new bucket to the begining of the list
while(i_free_bucket <= i_max_bucket) { long free_data = _table_hash_delta[i_free_bucket]; if(_NULL_HASH == (free_data >> _HASH_SHIFT)) {
//we found a free bucket outside of the cahce-line, so
//add the new bucket to the end of the list
while(i_free_bucket >= i_min_bucket) { long free_data = _table_hash_delta[i_free_bucket]; if(_NULL_HASH == (free_data >> _HASH_SHIFT)) {
//we found a free bucket outside of the cahce-line, so
//add the new bucket to the end of the list
private void optimize_cacheline_use(final int i_free_bucket) { final int i_start_cacheline = i_free_bucket & _NOT_CACHE_MASK; final int i_end_cacheline = i_start_cacheline + _CACHE_MASK;
//go over the buckets that reside in the cacheline of the free bucket for(int i_cacheline = i_start_cacheline; i_cacheline <= i_end_cacheline; ++i_cacheline) {
//check if current bucket has keys final long data = _table_hash_delta[i_cacheline]; final short first_delta = (short) data; if(_NULL_DELTA_INT != first_delta) {
int last_i_relocate = _NULL_INDX; int i_relocate = i_cacheline + first_delta; int curr_delta = first_delta;
//go over the keys in the bucket-list do {
//if the key reside outside the cahceline if(curr_delta < 0 || curr_delta > _CACHE_MASK) {
//copy key, value, & hash to the free bucket final int i_key_value = i_free_bucket << 1; final int i_rel_key_value = i_relocate << 1;
_table_key_value[i_key_value] = _table_key_value[i_rel_key_value];
_table_key_value[i_key_value + 1] = _table_key_value[i_rel_key_value + 1]; long relocate_data = _table_hash_delta[i_relocate]; long free_data = _table_hash_delta[i_free_bucket];
free_data &= _NOT_HASH_MASK;
free_data |= (relocate_data & _HASH_MASK);
//update the next-field of the free-bucket
free_data &= _NOT_NEXT_MASK; final int relocate_next_delta = (int) relocate_data >> _NEXT_SHIFT; if(_NULL_DELTA_INT == relocate_next_delta) {
free_data |= _NULL_DELTA_NEXT_LONG;
} else { final long new_next = (((i_relocate + relocate_next_delta) - i_free_bucket) & 0xFFFFL) << 16;
free_data |= new_next;
}
_table_hash_delta[i_free_bucket] = free_data;
//update the "first" or "next" field of the last if(_NULL_INDX == last_i_relocate) { long start_data = _table_hash_delta[i_cacheline] & _NOT_FIRST_MASK;
start_data |= ((i_free_bucket - i_cacheline) & 0xFFFFL);
_table_hash_delta[i_cacheline] = start_data;
} else { long last_data = _table_hash_delta[last_i_relocate]
& _NOT_NEXT_MASK;
last_data |= (((i_free_bucket - last_i_relocate) & 0xFFFFL) << 16);
_table_hash_delta[last_i_relocate] = last_data;
}
final long relocate_data = _table_hash_delta[i_relocate]; final int next_delta = (int) relocate_data >> _NEXT_SHIFT; if(_NULL_DELTA_INT == next_delta) break;
last_i_relocate = i_relocate;
curr_delta += next_delta;
i_relocate += next_delta;
} while(true);//for on list
}//if list exists
}//for on list
}
V remove(final K key, final int hash) {
lock(); try {
//go over the list and look for key final int i_start_bucket = hash & _bucketk_mask; int iBucket = i_start_bucket; long data = _table_hash_delta[iBucket]; final short first_delta = (short) data; if(0 != first_delta) { if(_NULL_DELTA_SHORT == first_delta) return null;
iBucket += first_delta;
data = _table_hash_delta[iBucket];
}
int i_last_bucket = -1; do { final int iRef; if(hash == (data >> _HASH_SHIFT)
&& key.equals(_table_key_value[iRef = (iBucket << 1)])) {
data &= _NOT_HASH_MASK; final int next_delta = (int) data >> _NEXT_SHIFT;
_table_hash_delta[iBucket] = data; //hash = null
_table_key_value[iRef] = null; //key = null;
final int iRef2 = iRef + 1; final V key_value = (V) _table_key_value[iRef2];
_table_key_value[iRef2] = null; //value = null;
private static final int hash(int h) {
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14); return h ^ (h >>> 16);
}
public ConcurrentHopscotchHashMap(final int initialCapacity, final int concurrencyLevel) {
//check for the validity of the algorithems if(initialCapacity < 0 || concurrencyLevel <= 0 /*|| machineCachelineSize <= 0*/) throw new IllegalArgumentException();
//set the user preference, should we force cache-line alignment
//_is_cacheline_alignment = isCachelineAlignment;
//allocate the segments array final int numSegments = nearestPowerOfTwo(concurrencyLevel);
_segment_mask = (numSegments - 1);
_segments = Segment.<K, V> newArray(numSegments);
// Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; while(ssize < numSegments) {
++sshift;
ssize <<= 1;
}
_segment_shift = 32 - sshift;
//initialize the segmens final int initCapacity = nearestPowerOfTwo(initialCapacity); final int segmentCapacity = initCapacity / numSegments; for(int iSeg = 0; iSeg < numSegments; ++iSeg) {
_segments[iSeg] = new Segment<K, V>(segmentCapacity);
}
}
public boolean isEmpty() { final Segment<K, V>[] segments = this._segments;
/*
* We keep track of per-segment "timestamp" to avoid ABA
* problems in which an element in one segment was added and
* in another removed during traversal, in which case the
* table was never actually empty at any point. Note the
* similar use of "timestamp" in the size() and containsValue()
* methods, which are the only other methods also susceptible
* to ABA problems.
*/ int[] mc = new int[segments.length]; int mcsum = 0; for(int i = 0; i < segments.length; ++i) { if(0 != segments[i]._count) return false; else
mcsum += mc[i] = segments[i]._timestamp;
}
// If mcsum happens to be zero, then we know we got a snapshot
// before any modifications at all were made. This is
// probably common enough to bother tracking. if(mcsum != 0) { for(int i = 0; i < segments.length; ++i) { if(0 != segments[i]._count || mc[i] != segments[i]._timestamp) return false;
}
} return true;
}
public int size() { final Segment<K, V>[] segments = this._segments; long sum = 0; long check = 0; int[] mc = new int[segments.length];
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking. for(int iTry = 0; iTry < _RETRIES_BEFORE_LOCK; ++iTry) {
check = 0;
sum = 0; int mcsum = 0; for(int i = 0; i < segments.length; ++i) {
sum += segments[i]._count;
mcsum += mc[i] = segments[i]._timestamp;
} if(mcsum != 0) { for(int i = 0; i < segments.length; ++i) {
check += segments[i]._count; if(mc[i] != segments[i]._timestamp) {
check = -1; // force retry break;
}
}
} if(check == sum) break;
}
if(check != sum) { // Resort to locking all segments
sum = 0; for(int i = 0; i < segments.length; ++i)
segments[i].lock(); for(int i = 0; i < segments.length; ++i)
sum += segments[i]._count; for(int i = 0; i < segments.length; ++i)
segments[i].unlock();
} if(sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int) sum;
}
//contains
public boolean containsKey(final K key) { final int hash = hash(key.hashCode()); return segmentFor(hash).containsKey(key, hash);
}
public V get(final K key) { final int hash = hash(key.hashCode()); return segmentFor(hash).get(key, hash);
}
//add public V put(K key, V value) { if(value == null) throw new NullPointerException(); final int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value);
}
//remove public V remove(final K key) { final int hash = hash(key.hashCode()); return segmentFor(hash).remove(key, hash);
}