org.jboss.marshalling.reflect.UnlockedHashMap Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including
all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and
Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.marshalling.reflect;
import static java.lang.Integer.bitCount;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* Lock-free concurrent hash map.
*
* @param the key type
* @param the value type
*
* @author David M. Lloyd
*/
final class UnlockedHashMap extends AbstractMap implements ConcurrentMap {
private static final int DEFAULT_INITIAL_CAPACITY = 512;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.60f;
/** A row which has been resized into the new view. */
@SuppressWarnings("unchecked")
private static final Item,?>[] RESIZED = new Item[0];
/** A non-existent table entry (as opposed to a {@code null} value). */
private static final Object NONEXISTENT = new Object();
private volatile Table table;
private final Set keySet = new KeySet();
private final Set> entrySet = new EntrySet();
private final Collection values = new Values();
private final float loadFactor;
private final int initialCapacity;
@SuppressWarnings("unchecked")
private static final AtomicIntegerFieldUpdater sizeUpdater = AtomicIntegerFieldUpdater.newUpdater(Table.class, "size");
@SuppressWarnings("unchecked")
private static final AtomicReferenceFieldUpdater tableUpdater = AtomicReferenceFieldUpdater.newUpdater(UnlockedHashMap.class, Table.class, "table");
@SuppressWarnings("unchecked")
private static final AtomicReferenceFieldUpdater- valueUpdater = AtomicReferenceFieldUpdater.newUpdater(Item.class, Object.class, "value");
/**
* Construct a new instance.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
*/
public UnlockedHashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Initial capacity must be > 0");
}
if (initialCapacity > MAXIMUM_CAPACITY) {
initialCapacity = MAXIMUM_CAPACITY;
}
if (loadFactor <= 0.0 || Float.isNaN(loadFactor) || loadFactor >= 1.0) {
throw new IllegalArgumentException("Load factor must be between 0.0f and 1.0f");
}
int capacity = 1;
while (capacity < initialCapacity) {
capacity <<= 1;
}
this.loadFactor = loadFactor;
this.initialCapacity = capacity;
final Table
table = new Table(capacity, loadFactor);
tableUpdater.set(this, table);
}
/**
* Construct a new instance.
*
* @param loadFactor the load factor
*/
public UnlockedHashMap(final float loadFactor) {
this(DEFAULT_INITIAL_CAPACITY, loadFactor);
}
/**
* Construct a new instance.
*
* @param initialCapacity the initial capacity
*/
public UnlockedHashMap(final int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Construct a new instance.
*/
public UnlockedHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
private Item[] addItem(final Item[] row, final Item newItem) {
if (row == null) {
return createRow(newItem);
} else {
final int length = row.length;
Item[] newRow = Arrays.copyOf(row, length + 1);
newRow[length] = newItem;
return newRow;
}
}
@SuppressWarnings("unchecked")
private static Item[] createRow(final Item newItem) {
return new Item[] { newItem };
}
@SuppressWarnings("unchecked")
private static Item[] createRow(final int length) {
return new Item[length];
}
private V doPut(K key, V value, boolean ifAbsent, Table table) {
final int hashCode = hashCode(key);
final int idx = hashCode & table.length() - 1;
OUTER: for (;;) {
// Fetch the table row.
Item[] oldRow = table.get(idx);
if (oldRow == RESIZED) {
// row was transported to the new table so recalculate everything
final V result = doPut(key, value, ifAbsent, table.resizeView);
// keep a consistent size view though!
if (result == NONEXISTENT) sizeUpdater.getAndIncrement(table);
return result;
}
if (oldRow != null) {
// Find the matching Item in the row.
Item oldItem = null;
for (Item tryItem : oldRow) {
if (key == tryItem.key) {
oldItem = tryItem;
break;
}
}
if (oldItem != null) {
// entry exists; try to return the old value and try to replace the value if allowed.
V oldItemValue;
do {
oldItemValue = oldItem.value;
if (oldItemValue == NONEXISTENT) {
// Key was removed; on the next iteration or two the doornail should be gone.
continue OUTER;
}
} while (! ifAbsent && ! valueUpdater.compareAndSet(oldItem, oldItemValue, value));
return oldItemValue;
}
// Row exists but item doesn't.
}
// Row doesn't exist, or row exists but item doesn't; try and add a new item to the row.
final Item newItem = new Item(key, hashCode, value);
final Item[] newRow = addItem(oldRow, newItem);
if (! table.compareAndSet(idx, oldRow, newRow)) {
// Nope, row changed; retry.
continue;
}
// Up the table size.
final int threshold = table.threshold;
int newSize = sizeUpdater.incrementAndGet(table);
// if table is resized, newSize will have the sign bit set and thus will be < 0
while (threshold < Integer.MAX_VALUE && newSize > threshold) {
if (sizeUpdater.compareAndSet(table, newSize, newSize | 0x80000000)) {
resize(table);
return nonexistent();
}
}
// Success.
return nonexistent();
}
}
private static int hashCode(final Object key) {
int h = key == null ? 0 : System.identityHashCode(key);
return h - (h << 7);
}
private void resize(Table origTable) {
final int origCapacity = origTable.length();
assert bitCount(origCapacity) == 1;
final Table newTable = new Table(origCapacity << 1, loadFactor);
// Prevent resize until we're done...
newTable.size = 0x80000000;
origTable.resizeView = newTable;
for (int i = 0; i < origCapacity; i ++) {
// for each row, try to resize into two new rows
Item[] origRow, newRow0 = null, newRow1 = null;
do {
origRow = origTable.get(i);
if (origRow != null) {
int count0 = 0, count1 = 0;
for (Item item : origRow) {
if ((item.hashCode & origCapacity) == 0) {
count0++;
} else {
count1++;
}
}
if (count0 != 0) {
newRow0 = createRow(count0);
int j = 0;
for (Item item : origRow) {
if ((item.hashCode & origCapacity) == 0) {
newRow0[j++] = item;
}
}
newTable.lazySet(i, newRow0);
} else {
newTable.lazySet(i, null);
}
if (count1 != 0) {
newRow1 = createRow(count1);
int j = 0;
for (Item item : origRow) {
if ((item.hashCode & origCapacity) != 0) {
newRow1[j++] = item;
}
}
newTable.lazySet(i + origCapacity, newRow1);
} else {
newTable.lazySet(i + origCapacity, null);
}
}
} while (! origTable.compareAndSet(i, origRow, UnlockedHashMap.resized()));
if (origRow != null) sizeUpdater.getAndAdd(newTable, origRow.length);
}
int size;
do {
size = newTable.size;
final int threshold = newTable.threshold;
if (threshold < Integer.MAX_VALUE && (size & 0x7fffffff) >= threshold) {
// time for another resize, right away
resize(newTable);
return;
}
} while (!sizeUpdater.compareAndSet(newTable, size, size & 0x7fffffff));
// All done, plug in the new table
table = newTable;
}
private static Item[] remove(Item[] row, int idx) {
final int len = row.length;
assert idx < len;
if (len == 1) {
return null;
}
@SuppressWarnings("unchecked")
Item[] newRow = new Item[len - 1];
if (idx > 0) {
System.arraycopy(row, 0, newRow, 0, idx);
}
if (idx < len - 1) {
System.arraycopy(row, idx + 1, newRow, idx, len - 1 - idx);
}
return newRow;
}
public V putIfAbsent(final K key, final V value) {
final V result = doPut(key, value, true, table);
return result == NONEXISTENT ? null : result;
}
public boolean remove(final Object objectKey, final Object objectValue) {
// Get type-safe key and value.
@SuppressWarnings("unchecked")
final K key = (K) objectKey;
@SuppressWarnings("unchecked")
final V value = (V) objectValue;
return doRemove(key, value, table);
}
private boolean doRemove(final Item item, final Table table) {
int hashCode = item.hashCode;
final int idx = hashCode & table.length() - 1;
Item[] oldRow;
for (;;) {
oldRow = table.get(idx);
if (oldRow == null) {
return false;
}
if (oldRow == RESIZED) {
boolean result;
if (result = doRemove(item, table.resizeView)) {
sizeUpdater.getAndDecrement(table);
}
return result;
}
int rowIdx = -1;
for (int i = 0; i < oldRow.length; i ++) {
if (item == oldRow[i]) {
rowIdx = i;
break;
}
}
if (rowIdx == -1) {
return false;
}
if (table.compareAndSet(idx, oldRow, remove(oldRow, rowIdx))) {
sizeUpdater.getAndDecrement(table);
return true;
}
// row changed, cycle back again
}
}
private boolean doRemove(final K key, final V value, final Table table) {
final int hashCode = hashCode(key);
final int idx = hashCode & table.length() - 1;
Item[] oldRow;
// Fetch the table row.
oldRow = table.get(idx);
if (oldRow == null) {
// no match for the key
return false;
}
if (oldRow == RESIZED) {
boolean result;
if (result = doRemove(key, value, table.resizeView)) {
// keep size consistent
sizeUpdater.getAndDecrement(table);
}
return result;
}
// Find the matching Item in the row.
Item oldItem = null;
V oldValue = null;
int rowIdx = -1;
for (int i = 0; i < oldRow.length; i ++) {
Item tryItem = oldRow[i];
if (key == tryItem.key) {
final Object v2 = oldValue = tryItem.value;
if (value == v2) {
oldItem = tryItem;
rowIdx = i;
break;
} else {
// value doesn't match; exit without changing map.
return false;
}
}
}
if (oldItem == null) {
// no such entry exists.
return false;
}
if (! valueUpdater.compareAndSet(oldItem, oldValue, NONEXISTENT)) {
// Value was changed to a non-equal value.
return false;
}
// Now we are free to remove the item from the row.
if (table.compareAndSet(idx, oldRow, remove(oldRow, rowIdx))) {
// Adjust the table size, since we are definitely the ones to be removing this item from the table.
sizeUpdater.decrementAndGet(table);
return true;
} else {
// The old row changed so retry by the other algorithm
return doRemove(oldItem, table);
}
}
@SuppressWarnings("unchecked")
public V remove(final Object objectKey) {
final V result = doRemove((K) objectKey, table);
return result == NONEXISTENT ? null : result;
}
private V doRemove(final K key, final Table table) {
final int hashCode = hashCode(key);
final int idx = hashCode & table.length() - 1;
// Fetch the table row.
Item[] oldRow = table.get(idx);
if (oldRow == null) {
// no match for the key
return nonexistent();
}
if (oldRow == RESIZED) {
V result;
if ((result = doRemove(key, table.resizeView)) != NONEXISTENT) {
// keep size consistent
sizeUpdater.getAndDecrement(table);
}
return result;
}
// Find the matching Item in the row.
Item oldItem = null;
int rowIdx = -1;
for (int i = 0; i < oldRow.length; i ++) {
Item tryItem = oldRow[i];
if (key == tryItem.key) {
oldItem = tryItem;
rowIdx = i;
break;
}
}
if (oldItem == null) {
// no such entry exists.
return nonexistent();
}
// Mark the item as "removed".
@SuppressWarnings("unchecked")
V oldValue = (V) valueUpdater.getAndSet(oldItem, NONEXISTENT);
if (oldValue == NONEXISTENT) {
// Someone else beat us to it.
return nonexistent();
}
// Now we are free to remove the item from the row.
if (table.compareAndSet(idx, oldRow, remove(oldRow, rowIdx))) {
// Adjust the table size, since we are definitely the ones to be removing this item from the table.
sizeUpdater.decrementAndGet(table);
// Item is removed from the row; we are done here.
return oldValue;
} else {
boolean result = doRemove(oldItem, table);
assert result;
return oldValue;
}
}
@SuppressWarnings("unchecked")
private static V nonexistent() {
return (V) NONEXISTENT;
}
@SuppressWarnings("unchecked")
private static Item[] resized() {
return (Item[]) RESIZED;
}
public boolean replace(final K key, final V oldValue, final V newValue) {
return doReplace(key, oldValue, newValue, table);
}
private boolean doReplace(final K key, final V oldValue, final V newValue, final Table table) {
final int hashCode = hashCode(key);
final int idx = hashCode & table.length() - 1;
// Fetch the table row.
Item[] oldRow = table.get(idx);
if (oldRow == null) {
// no match for the key
return false;
}
if (oldRow == RESIZED) {
return doReplace(key, oldValue, newValue, table.resizeView);
}
// Find the matching Item in the row.
Item oldItem = null;
V oldRowValue = null;
for (Item tryItem : oldRow) {
if (key == tryItem.key) {
final Object v2 = oldRowValue = tryItem.value;
if (oldValue == v2) {
oldItem = tryItem;
break;
} else {
// value doesn't match; exit without changing map.
return false;
}
}
}
if (oldItem == null) {
// no such entry exists.
return false;
}
// Now swap the item.
return valueUpdater.compareAndSet(oldItem, oldRowValue, newValue);
}
public V replace(final K key, final V value) {
final V result = doReplace(key, value, table);
return result == NONEXISTENT ? null : result;
}
private V doReplace(final K key, final V value, final Table table) {
final int hashCode = hashCode(key);
final int idx = hashCode & table.length() - 1;
// Fetch the table row.
Item[] oldRow = table.get(idx);
if (oldRow == null) {
// no match for the key
return nonexistent();
}
if (oldRow == RESIZED) {
return doReplace(key, value, table.resizeView);
}
// Find the matching Item in the row.
Item oldItem = null;
for (Item tryItem : oldRow) {
if (key == tryItem.key) {
oldItem = tryItem;
break;
}
}
if (oldItem == null) {
// no such entry exists.
return nonexistent();
}
// Now swap the item.
@SuppressWarnings("unchecked")
V oldRowValue = (V) valueUpdater.getAndSet(oldItem, value);
if (oldRowValue == NONEXISTENT) {
// Item was removed.
return nonexistent();
}
// Item is swapped; we are done here.
return oldRowValue;
}
public int size() {
return table.size & 0x7fffffff;
}
private V doGet(final Table table, final K key) {
final Item[] row = table.get(hashCode(key) & (table.length() - 1));
if (row != null) for (Item item : row) {
if (key == item.key) {
return item.value;
}
}
return nonexistent();
}
public boolean containsKey(final Object key) {
@SuppressWarnings("unchecked")
final V value = doGet(table, (K) key);
return value != NONEXISTENT;
}
public V get(final Object key) {
@SuppressWarnings("unchecked")
final V value = doGet(table, (K) key);
return value == NONEXISTENT ? null : value;
}
public V put(final K key, final V value) {
final V result = doPut(key, value, false, table);
return result == NONEXISTENT ? null : result;
}
public void clear() {
table = new Table(initialCapacity, loadFactor);
}
public Set> entrySet() {
return entrySet;
}
public Collection values() {
return values;
}
public Set keySet() {
return keySet;
}
final class KeySet extends AbstractSet implements Set {
public void clear() {
UnlockedHashMap.this.clear();
}
public boolean contains(final Object o) {
return containsKey(o);
}
@SuppressWarnings("unchecked")
public boolean remove(final Object o) {
return doRemove((K) o, table) != NONEXISTENT;
}
public Iterator iterator() {
return new KeyIterator();
}
public Object[] toArray() {
ArrayList