/* * JBoss, Home of Professional Open Source. * Copyright 2011, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.remoting3._private; import java.util.AbstractCollection; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceArray; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.IntFunction; import java.util.function.ToIntFunction; /** * Lock-free concurrent integer-indexed hash map. * * @param <V> the value type * * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a> */ public final class IntIndexHashMap<V> extends AbstractCollection<V> implements IntIndexMap<V> { 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. */ private static final Object[] RESIZED = new Object[0]; /** A non-existent table entry (as opposed to a {@code null} value). */ private static final Object NONEXISTENT = new Object(); private final ToIntFunction<? super V> indexer; private final Equaller<? super V> ve; private volatile Table<V> table; private final float loadFactor; private final int initialCapacity; @SuppressWarnings({"unchecked", "rawtypes"}) private static final AtomicIntegerFieldUpdater<Table> sizeUpdater = AtomicIntegerFieldUpdater.newUpdater(Table.class, "size"); @SuppressWarnings({"unchecked", "rawtypes"}) private static final AtomicReferenceFieldUpdater<IntIndexHashMap, Table> tableUpdater = AtomicReferenceFieldUpdater.newUpdater(IntIndexHashMap.class, Table.class, "table"); /** * Construct a new instance. * * @param indexer the key indexer * @param valueEqualler the value equaller * @param initialCapacity the initial capacity * @param loadFactor the load factor */ public IntIndexHashMap(ToIntFunction<? super V> indexer, Equaller<? super V> valueEqualler, int initialCapacity, float loadFactor) { if (valueEqualler == null) { throw new IllegalArgumentException("valueEqualler is null"); } this.indexer = indexer; ve = valueEqualler; 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<V> table = new Table<V>(capacity, loadFactor); tableUpdater.set(this, table); } /** * Construct a new instance. * * @param indexer the key indexer * @param valueEqualler the value equaller */ public IntIndexHashMap(ToIntFunction<? super V> indexer, Equaller<? super V> valueEqualler) { this(indexer, valueEqualler, DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } /** * Construct a new instance. * * @param indexer the key indexer * @param initialCapacity the initial capacity * @param loadFactor the load factor */ public IntIndexHashMap(ToIntFunction<? super V> indexer, int initialCapacity, final float loadFactor) { this(indexer, Equaller.DEFAULT, initialCapacity, loadFactor); } /** * Construct a new instance. * * @param indexer the key indexer * @param loadFactor the load factor */ public IntIndexHashMap(ToIntFunction<? super V> indexer, final float loadFactor) { this(indexer, DEFAULT_INITIAL_CAPACITY, loadFactor); } /** * Construct a new instance. * * @param indexer the key indexer * @param initialCapacity the initial capacity */ public IntIndexHashMap(ToIntFunction<? super V> indexer, final int initialCapacity) { this(indexer, initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Construct a new instance. * * @param indexer the key indexer */ public IntIndexHashMap(ToIntFunction<? super V> indexer) { this(indexer, DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } public V putIfAbsent(final V value) { final V result = doPut(value, true, table); return result == NONEXISTENT ? null : result; } public V computeIfAbsent(final int index, final IntFunction<V> producer) { final Table<V> table = this.table; V result = doGet(table, index); if (result == NONEXISTENT) { final V newVal = producer.apply(index); result = doPut(newVal, true, table); return result == NONEXISTENT ? newVal : result; } else { return result; } } public V removeKey(final int index) { final V result = doRemove(index, table); return result == NONEXISTENT ? null : result; } @SuppressWarnings({ "unchecked" }) public boolean remove(final Object value) { return doRemove((V) value, table); } public boolean containsKey(final int index) { return doGet(table, index) != NONEXISTENT; } public V get(final int index) { final V result = doGet(table, index); return result == NONEXISTENT ? null : result; } public V put(final V value) { final V result = doPut(value, false, table); return result == NONEXISTENT ? null : result; } public V replace(final V value) { final V result = doReplace(value, table); return result == NONEXISTENT ? null : result; } public boolean replace(final V oldValue, final V newValue) { if (indexer.applyAsInt(oldValue) != indexer.applyAsInt(newValue)) { throw new IllegalArgumentException("Can only replace with value which has the same key"); } return doReplace(oldValue, newValue, table); } public int applyAsInt(final V argument) { return indexer.applyAsInt(argument); } public boolean add(final V v) { return doPut(v, true, table) == NONEXISTENT; } @SuppressWarnings("unchecked") public <T> T[] toArray(final T[] a) { final ArrayList<T> list = new ArrayList<T>(size()); for (final V item : this) { list.add((T) item); } return list.toArray(a); } public Object[] toArray() { final ArrayList<Object> list = new ArrayList<Object>(size()); for (V item : this) { list.add(item); } return list.toArray(); } @SuppressWarnings({ "unchecked" }) public boolean contains(final Object o) { return ve.equals((V) o, get(indexer.applyAsInt((V) o))); } public Iterator<V> iterator() { return new EntryIterator(); } public int size() { return table.size & 0x7fffffff; } private boolean doReplace(final V oldValue, final V newValue, final Table<V> table) { final int key = indexer.applyAsInt(oldValue); final AtomicReferenceArray<V[]> array = table.array; final int idx = key & array.length() - 1; OUTER: for (;;) { // Fetch the table row. V[] oldRow = array.get(idx); if (oldRow == null) { // no match for the key return false; } if (oldRow == RESIZED) { return doReplace(oldValue, newValue, table.resizeView); } for (int i = 0, length = oldRow.length; i < length; i++) { final V tryItem = oldRow[i]; if (ve.equals(tryItem, oldValue)) { final V[] newRow = oldRow.clone(); newRow[i] = newValue; if (array.compareAndSet(i, oldRow, newRow)) { return true; } else { continue OUTER; } } } return false; } } private V doReplace(final V value, final Table<V> table) { final int key = indexer.applyAsInt(value); final AtomicReferenceArray<V[]> array = table.array; final int idx = key & array.length() - 1; OUTER: for (;;) { // Fetch the table row. V[] oldRow = array.get(idx); if (oldRow == null) { // no match for the key return nonexistent(); } if (oldRow == RESIZED) { return doReplace(value, table.resizeView); } // Find the matching Item in the row. for (int i = 0, length = oldRow.length; i < length; i++) { final V tryItem = oldRow[i]; if (key == indexer.applyAsInt(tryItem)) { final V[] newRow = oldRow.clone(); newRow[i] = value; if (array.compareAndSet(i, oldRow, newRow)) { return tryItem; } else { continue OUTER; } } } return nonexistent(); } } private boolean doRemove(final V item, final Table<V> table) { int key = indexer.applyAsInt(item); final AtomicReferenceArray<V[]> array = table.array; final int idx = key & array.length() - 1; V[] oldRow; OUTER: for (;;) { oldRow = array.get(idx); if (oldRow == null) { return false; } if (oldRow == RESIZED) { boolean result; if (result = doRemove(item, table.resizeView)) { sizeUpdater.getAndDecrement(table); } return result; } for (int i = 0; i < oldRow.length; i ++) { if (ve.equals(item, oldRow[i])) { if (array.compareAndSet(idx, oldRow, remove(oldRow, i))) { sizeUpdater.getAndDecrement(table); return true; } else { continue OUTER; } } } // not found return false; } } private V doRemove(final int key, final Table<V> table) { final AtomicReferenceArray<V[]> array = table.array; final int idx = key & array.length() - 1; V[] oldRow; OUTER: for (;;) { oldRow = array.get(idx); if (oldRow == null) { return nonexistent(); } if (oldRow == RESIZED) { V result; if ((result = doRemove(key, table.resizeView)) != NONEXISTENT) { sizeUpdater.getAndDecrement(table); } return result; } for (int i = 0; i < oldRow.length; i ++) { if (key == indexer.applyAsInt(oldRow[i])) { if (array.compareAndSet(idx, oldRow, remove(oldRow, i))) { sizeUpdater.getAndDecrement(table); return oldRow[i]; } else { continue OUTER; } } } // not found return nonexistent(); } } private V doPut(V value, boolean ifAbsent, Table<V> table) { final int hashCode = indexer.applyAsInt(value); final AtomicReferenceArray<V[]> array = table.array; final int idx = hashCode & array.length() - 1; OUTER: for (;;) { // Fetch the table row. V[] oldRow = array.get(idx); if (oldRow == RESIZED) { // row was transported to the new table so recalculate everything final V result = doPut(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. V oldItem; for (int i = 0, length = oldRow.length; i < length; i++) { if (hashCode == indexer.applyAsInt(oldRow[i])) { if (ifAbsent) { return oldRow[i]; } else { V[] newRow = oldRow.clone(); newRow[i] = value; oldItem = oldRow[i]; if (array.compareAndSet(idx, oldRow, newRow)) { return oldItem; } else { // retry continue OUTER; } } } } } if (array.compareAndSet(idx, oldRow, addItem(oldRow, value))) { // Up the table size. final int threshold = table.threshold; int newSize = sizeUpdater.incrementAndGet(table); // if the sign bit is set the value will be < 0 meaning if a resize is in progress this condition is false while (newSize > threshold) { if (sizeUpdater.compareAndSet(table, newSize, newSize | 0x80000000)) { resize(table); break; } else { newSize = table.size; } } // Success. return nonexistent(); } } } private void resize(Table<V> origTable) { final AtomicReferenceArray<V[]> origArray = origTable.array; final int origCapacity = origArray.length(); final Table<V> newTable = new Table<V>(origCapacity << 1, loadFactor); // Prevent resize until we're done... newTable.size = 0x80000000; origTable.resizeView = newTable; final AtomicReferenceArray<V[]> newArray = newTable.array; for (int i = 0; i < origCapacity; i ++) { // for each row, try to resize into two new rows V[] origRow, newRow0, newRow1; int count0, count1; do { count0 = count1 = 0; origRow = origArray.get(i); if (origRow != null) { for (V item : origRow) { if ((indexer.applyAsInt(item) & origCapacity) == 0) { count0++; } else { count1++; } } if (count0 != 0) { newRow0 = createRow(count0); int j = 0; for (V item : origRow) { if ((indexer.applyAsInt(item) & origCapacity) == 0) { newRow0[j++] = item; } } newArray.lazySet(i, newRow0); } if (count1 != 0) { newRow1 = createRow(count1); int j = 0; for (V item : origRow) { if ((indexer.applyAsInt(item) & origCapacity) != 0) { newRow1[j++] = item; } } newArray.lazySet(i + origCapacity, newRow1); } } } while (! origArray.compareAndSet(i, origRow, IntIndexHashMap.<V>resized())); sizeUpdater.getAndAdd(newTable, count0 + count1); } int size; do { size = newTable.size; if ((size & 0x7fffffff) >= newTable.threshold) { // shorter path for reads and writes table = newTable; // then 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 <V> V[] remove(V[] row, int idx) { final int len = row.length; assert idx < len; if (len == 1) { return null; } V[] newRow = createRow(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; } private V doGet(final Table<V> table, final int key) { final AtomicReferenceArray<V[]> array = table.array; final V[] row = array.get(key & (array.length() - 1)); if (row != null) for (V item : row) { if (key == indexer.applyAsInt(item)) { return item; } } return nonexistent(); } public void clear() { table = new Table<V>(initialCapacity, loadFactor); } private static <V> V[] addItem(final V[] row, final V newItem) { if (row == null) { return createRow(newItem); } else { final int length = row.length; V[] newRow = Arrays.copyOf(row, length + 1); newRow[length] = newItem; return newRow; } } @SuppressWarnings("unchecked") private static <V> V[] createRow(final V newItem) { return (V[]) new Object[] { newItem }; } @SuppressWarnings("unchecked") private static <V> V[] createRow(final int length) { return (V[]) new Object[length]; } @SuppressWarnings("unchecked") private static <V> V nonexistent() { return (V) NONEXISTENT; } @SuppressWarnings("unchecked") private static <V> V[] resized() { return (V[]) RESIZED; } final class RowIterator implements Iterator<V> { private final Table<V> table; V[] row; private int idx; private int removeIdx = -1; private V next = nonexistent(); RowIterator(final Table<V> table, final V[] row) { this.table = table; this.row = row; } public boolean hasNext() { while (next == NONEXISTENT) { final V[] row = this.row; if (row == null || idx == row.length) { return false; } next = row[idx++]; } return true; } public V next() { if (hasNext()) try { removeIdx = idx - 1; return next; } finally { next = nonexistent(); } throw new NoSuchElementException(); } public void remove() { int removeIdx = this.removeIdx; this.removeIdx = -1; if (removeIdx == -1) { throw new IllegalStateException("next() not yet called"); } doRemove(row[removeIdx], table); } } final class BranchIterator implements Iterator<V> { private final Iterator<V> branch0; private final Iterator<V> branch1; private boolean branch; BranchIterator(final Iterator<V> branch0, final Iterator<V> branch1) { this.branch0 = branch0; this.branch1 = branch1; } public boolean hasNext() { return branch0.hasNext() || branch1.hasNext(); } public V next() { if (branch) { return branch1.next(); } if (branch0.hasNext()) { return branch0.next(); } else { branch = true; return branch1.next(); } } public void remove() { if (branch) { branch0.remove(); } else { branch1.remove(); } } } private Iterator<V> createRowIterator(Table<V> table, int rowIdx) { final AtomicReferenceArray<V[]> array = table.array; final V[] row = array.get(rowIdx); if (row == RESIZED) { final Table<V> resizeView = table.resizeView; return new BranchIterator(createRowIterator(resizeView, rowIdx), createRowIterator(resizeView, rowIdx + array.length())); } else { return new RowIterator(table, row); } } final class EntryIterator implements Iterator<V> { private final Table<V> table = IntIndexHashMap.this.table; private Iterator<V> tableIterator; private Iterator<V> removeIterator; private int tableIdx; private V next; public boolean hasNext() { while (next == null) { if (tableIdx == table.array.length()) { return false; } if (tableIterator == null) { tableIterator = createRowIterator(table, tableIdx++); } if (tableIterator.hasNext()) { next = tableIterator.next(); return true; } else { tableIterator = null; } } return true; } public V next() { if (hasNext()) try { return next; } finally { removeIterator = tableIterator; next = null; } throw new NoSuchElementException(); } public void remove() { final Iterator<V> removeIterator = this.removeIterator; if (removeIterator == null) { throw new IllegalStateException(); } else try { removeIterator.remove(); } finally { this.removeIterator = null; } } } static final class Table<V> { final AtomicReferenceArray<V[]> array; final int threshold; /** Bits 0-30 are size; bit 31 is 1 if the table is being resized. */ volatile int size; volatile Table<V> resizeView; private Table(int capacity, float loadFactor) { array = new AtomicReferenceArray<V[]>(capacity); threshold = capacity == MAXIMUM_CAPACITY ? Integer.MAX_VALUE : (int)(capacity * loadFactor); } } }