Concurrent hash set that allows the lock array to be resized.
/*
* RefinableHashSet.java
*
* Created on November 15, 2006, 3:59 PM
*
* From "The Art of Multiprocessor Programming",
* by Maurice Herlihy and Nir Shavit.
*
* This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.
* http://i.creativecommons.org/l/by-sa/3.0/us/88x31.png
*/
//package xbird.util.concurrent.set;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* Concurrent hash set that allows the lock array to be resized.
* @param <T> type
* @author Maurice Herlihy
*/
public class RefinableHashSet<T> extends BaseHashSet<T> {
AtomicMarkableReference<Thread> owner;
volatile ReentrantLock[] locks;
/**
* Concurrent Cuckoo hash set. Resizes lock array.
* @param capacity Initial number of buckets.
*/
public RefinableHashSet(int capacity) {
super(capacity);
locks = new ReentrantLock[capacity];
for(int j = 0; j < capacity; j++) {
locks[j] = new ReentrantLock();
}
owner = new AtomicMarkableReference<Thread>(null, false);
}
/**
* Synchronize before adding, removing, or testing for item
* @param x item involved
*/
@Override
public void acquire(T x) {
boolean[] mark = { true };
Thread me = Thread.currentThread();
Thread who;
while(true) {
do { // wait until not resizing
who = owner.get(mark);
} while(mark[0] && who != me);
ReentrantLock[] oldLocks = this.locks;
int myBucket = Math.abs(x.hashCode() % oldLocks.length);
ReentrantLock oldLock = oldLocks[myBucket];
oldLock.lock(); // acquire lock
who = owner.get(mark);
if((!mark[0] || who == me) && this.locks == oldLocks) { // recheck
return;
} else { // unlock & try again
oldLock.unlock();
}
}
}
/**
* synchronize after adding, removing, or testing for item
* @param x item involved
*/
@Override
public void release(T x) {
int myBucket = Math.abs(x.hashCode() % locks.length);
locks[myBucket].unlock();
}
/**
* Ensure that no thread is currently locking the set.
*/
protected void quiesce() {
for(ReentrantLock lock : locks) {
while(lock.isLocked()) {
} // spin
}
}
/**
* double the set size
*/
@Override
public void resize() {
int oldCapacity = table.length;
int newCapacity = 2 * oldCapacity;
Thread me = Thread.currentThread();
if(owner.compareAndSet(null, me, false, true)) {
try {
if(table.length != oldCapacity) { // someone else resized first
return;
}
quiesce();
List<T>[] oldTable = table;
table = (List<T>[]) new List[newCapacity];
for(int i = 0; i < newCapacity; i++)
table[i] = new ArrayList<T>();
locks = new ReentrantLock[newCapacity];
for(int j = 0; j < locks.length; j++) {
locks[j] = new ReentrantLock();
}
initializeFrom(oldTable);
} finally {
owner.set(null, false); // restore prior state
}
}
}
@Override
public boolean policy() {
return size / table.length > 4;
}
private void initializeFrom(List<T>[] oldTable) {
for(List<T> bucket : oldTable) {
for(T x : bucket) {
int myBucket = Math.abs(x.hashCode() % table.length);
table[myBucket].add(x);
}
}
}
}
/**
* Simple fine-grained hash map.
* @param <T> type
* @author Maurice Herlihy
*/
abstract class BaseHashSet<T> {
protected List<T>[] table;
protected int size;
public BaseHashSet(int capacity) {
size = 0;
table = (List<T>[]) new List[capacity];
for(int i = 0; i < capacity; i++) {
table[i] = new ArrayList<T>();
}
}
/**
* Is item in set?
* @param x item to test
* @return <code>true</code> iff item present
*/
public boolean contains(T x) {
acquire(x);
try {
int myBucket = Math.abs(x.hashCode() % table.length);
return table[myBucket].contains(x);
} finally {
release(x);
}
}
/**
* Add item to set
* @param x item to add
* @return <code>true</code> iff set changed
*/
public boolean add(T x) {
boolean result = false;
acquire(x);
try {
int myBucket = Math.abs(x.hashCode() % table.length);
result = table[myBucket].add(x);
size = result ? size + 1 : size;
} finally {
release(x); // always unlock
}
if(policy())
resize();
return result;
}
/**
* Remove item from set
* @param x item to remove
* @return <code>true</code> iff set changed
*/
public boolean remove(T x) {
acquire(x);
try {
int myBucket = Math.abs(x.hashCode() % table.length);
boolean result = table[myBucket].remove(x);
size = result ? size - 1 : size;
return result;
} finally {
release(x); // always unlock
}
}
/**
* Synchronize before adding, removing, or testing for item
* @param x item involved
*/
public abstract void acquire(T x);
/**
* synchronize after adding, removing, or testing for item
* @param x item involved
*/
public abstract void release(T x);
/**
* double the set size
*/
public abstract void resize();
/**
* decide whether to resize
* @return whether to resize
*/
public abstract boolean policy();
}
Related examples in the same category