All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.activemq.artemis.utils.collections.ConcurrentLongHashMap Maven / Gradle / Ivy

There is a newer version: 2.38.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.activemq.artemis.utils.collections;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.locks.StampedLock;
import java.util.function.LongFunction;

import static org.apache.activemq.artemis.utils.Preconditions.checkArgument;
import static org.apache.activemq.artemis.utils.Preconditions.checkNotNull;

/**
 * Map from long to an Object.
 *
 * Provides similar methods as a {@literal ConcurrentMap} with 2 differences:
 * 
    *
  1. No boxing/unboxing from {@literal long -> Long} *
  2. Open hash map with linear probing, no node allocations to store the values *
* * @param */ @SuppressWarnings("unchecked") public class ConcurrentLongHashMap { private static final Object EmptyValue = null; private static final Object DeletedValue = new Object(); private static final float MapFillFactor = 0.66f; private static final int DefaultExpectedItems = 256; private static final int DefaultConcurrencyLevel = 16; private final Section[] sections; public ConcurrentLongHashMap() { this(DefaultExpectedItems); } public ConcurrentLongHashMap(int expectedItems) { this(expectedItems, DefaultConcurrencyLevel); } public ConcurrentLongHashMap(int expectedItems, int numSections) { checkArgument(numSections > 0); if (expectedItems < numSections) { expectedItems = numSections; } int perSectionExpectedItems = expectedItems / numSections; int perSectionCapacity = (int) (perSectionExpectedItems / MapFillFactor); this.sections = (Section[]) new Section[numSections]; for (int i = 0; i < numSections; i++) { sections[i] = new Section<>(perSectionCapacity); } } public int size() { int size = 0; for (Section s : sections) { //read-acquire s.size that was write-released by s.unlockWrite s.tryOptimisticRead(); //a stale value won't hurt: anyway it's subject to concurrent modifications size += s.size; } return size; } long getUsedBucketCount() { long usedBucketCount = 0; for (Section s : sections) { usedBucketCount += s.usedBuckets; } return usedBucketCount; } public long capacity() { long capacity = 0; for (Section s : sections) { capacity += s.capacity; } return capacity; } public boolean isEmpty() { for (Section s : sections) { //read-acquire s.size that was write-released by s.unlockWrite s.tryOptimisticRead(); //a stale value won't hurt: anyway it's subject to concurrent modifications if (s.size != 0) { return false; } } return true; } public V get(long key) { long h = hash(key); return getSection(h).get(key, (int) h); } public boolean containsKey(long key) { return get(key) != null; } public V put(long key, V value) { checkNotNull(value); long h = hash(key); return getSection(h).put(key, value, (int) h, false, null); } public V putIfAbsent(long key, V value) { checkNotNull(value); long h = hash(key); return getSection(h).put(key, value, (int) h, true, null); } public V computeIfAbsent(long key, LongFunction provider) { checkNotNull(provider); long h = hash(key); return getSection(h).put(key, null, (int) h, true, provider); } public V remove(long key) { long h = hash(key); return getSection(h).remove(key, null, (int) h); } public boolean remove(long key, Object value) { checkNotNull(value); long h = hash(key); return getSection(h).remove(key, value, (int) h) != null; } private Section getSection(long hash) { // Use 32 msb out of long to get the section final int sectionIdx = (int) (hash >>> 32) & (sections.length - 1); return sections[sectionIdx]; } public void clear() { for (Section s : sections) { s.clear(); } } public void forEach(EntryProcessor processor) { for (Section s : sections) { s.forEach(processor); } } /** * @return a new list of all keys (makes a copy) */ public List keys() { List keys = new ArrayList<>(size()); forEach((key, value) -> keys.add(key)); return keys; } public ConcurrentLongHashSet keysLongHashSet() { ConcurrentLongHashSet concurrentLongHashSet = new ConcurrentLongHashSet(size()); forEach((key, value) -> concurrentLongHashSet.add(key)); return concurrentLongHashSet; } public List values() { List values = new ArrayList<>(size()); forEach((key, value) -> values.add(value)); return values; } public interface EntryProcessor { void accept(long key, V value); } // A section is a portion of the hash map that is covered by a single @SuppressWarnings("serial") private static final class Section extends StampedLock { private static final AtomicIntegerFieldUpdater
CAPACITY_UPDATER = AtomicIntegerFieldUpdater.newUpdater(Section.class, "capacity"); private long[] keys; private V[] values; private volatile int capacity; private int size; private int usedBuckets; private int resizeThreshold; Section(int capacity) { this.capacity = alignToPowerOfTwo(capacity); this.keys = new long[this.capacity]; this.values = (V[]) new Object[this.capacity]; this.size = 0; this.usedBuckets = 0; this.resizeThreshold = (int) (this.capacity * MapFillFactor); } @SuppressWarnings("NonAtomicVolatileUpdate") V get(long key, int keyHash) { int bucket = keyHash; long stamp = tryOptimisticRead(); boolean acquiredLock = false; try { while (true) { int capacity = this.capacity; bucket = signSafeMod(bucket, capacity); // First try optimistic locking long storedKey = keys[bucket]; V storedValue = values[bucket]; if (!acquiredLock && validate(stamp)) { // The values we have read are consistent if (storedKey == key) { return storedValue != DeletedValue ? storedValue : null; } else if (storedValue == EmptyValue) { // Not found return null; } } else { // Fallback to acquiring read lock if (!acquiredLock) { stamp = readLock(); acquiredLock = true; storedKey = keys[bucket]; storedValue = values[bucket]; } if (capacity != this.capacity) { // There has been a rehashing. We need to restart the search bucket = keyHash; continue; } if (storedKey == key) { return storedValue != DeletedValue ? storedValue : null; } else if (storedValue == EmptyValue) { // Not found return null; } } ++bucket; } } finally { if (acquiredLock) { unlockRead(stamp); } } } @SuppressWarnings("NonAtomicVolatileUpdate") V put(long key, V value, int keyHash, boolean onlyIfAbsent, LongFunction valueProvider) { int bucket = keyHash; long stamp = writeLock(); int capacity = this.capacity; // Remember where we find the first available spot int firstDeletedKey = -1; try { while (true) { bucket = signSafeMod(bucket, capacity); long storedKey = keys[bucket]; V storedValue = values[bucket]; if (storedKey == key) { if (storedValue == EmptyValue) { values[bucket] = value != null ? value : (valueProvider != null ? valueProvider.apply(key) : null); ++size; ++usedBuckets; return valueProvider != null ? values[bucket] : null; } else if (storedValue == DeletedValue) { values[bucket] = value != null ? value : (valueProvider != null ? valueProvider.apply(key) : null); ++size; return valueProvider != null ? values[bucket] : null; } else if (!onlyIfAbsent) { // Over written an old value for same key values[bucket] = value; return storedValue; } else { return storedValue; } } else if (storedValue == EmptyValue) { // Found an empty bucket. This means the key is not in the map. If we've already seen a deleted // key, we should write at that position if (firstDeletedKey != -1) { bucket = firstDeletedKey; } else { ++usedBuckets; } keys[bucket] = key; values[bucket] = value != null ? value : (valueProvider != null ? valueProvider.apply(key) : null); ++size; return valueProvider != null ? values[bucket] : null; } else if (storedValue == DeletedValue) { // The bucket contained a different deleted key if (firstDeletedKey == -1) { firstDeletedKey = bucket; } } ++bucket; } } finally { if (usedBuckets > resizeThreshold) { try { rehash(); } finally { unlockWrite(stamp); } } else { unlockWrite(stamp); } } } @SuppressWarnings("NonAtomicVolatileUpdate") private V remove(long key, Object value, int keyHash) { int bucket = keyHash; long stamp = writeLock(); try { while (true) { int capacity = this.capacity; bucket = signSafeMod(bucket, capacity); long storedKey = keys[bucket]; V storedValue = values[bucket]; if (storedKey == key) { if (value == null || value.equals(storedValue)) { if (storedValue == EmptyValue || storedValue == DeletedValue) { return null; } --size; V nextValueInArray = values[signSafeMod(bucket + 1, capacity)]; if (nextValueInArray == EmptyValue) { values[bucket] = (V) EmptyValue; --usedBuckets; } else { values[bucket] = (V) DeletedValue; } return storedValue; } else { return null; } } else if (storedValue == EmptyValue) { // Key wasn't found return null; } ++bucket; } } finally { unlockWrite(stamp); } } void clear() { long stamp = writeLock(); try { Arrays.fill(keys, 0); Arrays.fill(values, EmptyValue); this.size = 0; this.usedBuckets = 0; } finally { unlockWrite(stamp); } } public void forEach(EntryProcessor processor) { long stamp = tryOptimisticRead(); int capacity = this.capacity; long[] keys = this.keys; V[] values = this.values; boolean acquiredReadLock = false; try { // Validate no rehashing if (!validate(stamp)) { // Fallback to read lock stamp = readLock(); acquiredReadLock = true; capacity = this.capacity; keys = this.keys; values = this.values; } // Go through all the buckets for this section for (int bucket = 0; bucket < capacity; bucket++) { long storedKey = keys[bucket]; V storedValue = values[bucket]; if (!acquiredReadLock && !validate(stamp)) { // Fallback to acquiring read lock stamp = readLock(); acquiredReadLock = true; storedKey = keys[bucket]; storedValue = values[bucket]; } if (storedValue != DeletedValue && storedValue != EmptyValue) { processor.accept(storedKey, storedValue); } } } finally { if (acquiredReadLock) { unlockRead(stamp); } } } private void rehash() { // Expand the hashmap int newCapacity = capacity * 2; long[] newKeys = new long[newCapacity]; V[] newValues = (V[]) new Object[newCapacity]; // Re-hash table for (int i = 0; i < keys.length; i++) { long storedKey = keys[i]; V storedValue = values[i]; if (storedValue != EmptyValue && storedValue != DeletedValue) { insertKeyValueNoLock(newKeys, newValues, storedKey, storedValue); } } keys = newKeys; values = newValues; usedBuckets = size; CAPACITY_UPDATER.lazySet(this, newCapacity); resizeThreshold = (int) (newCapacity * MapFillFactor); } private static void insertKeyValueNoLock(long[] keys, V[] values, long key, V value) { int bucket = (int) hash(key); while (true) { bucket = signSafeMod(bucket, keys.length); V storedValue = values[bucket]; if (storedValue == EmptyValue) { // The bucket is empty, so we can use it keys[bucket] = key; values[bucket] = value; return; } ++bucket; } } } private static final long HashMixer = 0xc6a4a7935bd1e995L; private static final int R = 47; static long hash(long key) { long hash = key * HashMixer; hash ^= hash >>> R; hash *= HashMixer; return hash; } static int signSafeMod(long n, int Max) { return (int) n & (Max - 1); } static int alignToPowerOfTwo(int n) { return (int) Math.pow(2, 32 - Integer.numberOfLeadingZeros(n - 1)); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy