org.terracotta.offheapstore.concurrent.AbstractConcurrentOffHeapMap Maven / Gradle / Ivy
Show all versions of offheap-store Show documentation
/*
* Copyright 2015 Terracotta, Inc., a Software AG company.
*
* 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.terracotta.offheapstore.concurrent;
import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import org.terracotta.offheapstore.HashingMap;
import org.terracotta.offheapstore.OffHeapHashMap;
import org.terracotta.offheapstore.Segment;
import org.terracotta.offheapstore.MapInternals;
import org.terracotta.offheapstore.MetadataTuple;
import org.terracotta.offheapstore.exceptions.OversizeMappingException;
import org.terracotta.offheapstore.util.Factory;
/**
* An abstract concurrent (striped) off-heap map.
*
* This is an n-way hashcode striped map implementation. Subclasses must
* provide a {@link Factory} instance at construction time from which
* the required number of segments are created.
*
* @param the type of keys maintained by this map
* @param the type of mapped values
*
* @author Chris Dennis
*/
public abstract class AbstractConcurrentOffHeapMap extends AbstractMap implements ConcurrentMap, ConcurrentMapInternals, HashingMap {
private static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
private static final int DEFAULT_CONCURRENCY = 16;
protected final Segment[] segments;
private final int segmentShift;
private final int segmentMask;
private Set keySet;
private Set> entrySet;
private Collection values;
/**
* Create a concurrent map using a default number of segments.
*
* @param segmentFactory factory used to create the map segments
*/
public AbstractConcurrentOffHeapMap(Factory extends Segment> segmentFactory) {
this(segmentFactory, DEFAULT_CONCURRENCY);
}
/**
* Create a concurrent map with a defined number of segments.
*
* @param segmentFactory factory used to create the map segments
* @param concurrency number of segments in the map
* @throws IllegalArgumentException if the supplied number of segments is
* negative
*/
@SuppressWarnings("unchecked")
public AbstractConcurrentOffHeapMap(Factory extends Segment> segmentFactory, int concurrency) {
if (concurrency <= 0) {
throw new IllegalArgumentException("Concurrency must be positive [was: " + concurrency + "]");
}
if (concurrency > MAX_SEGMENTS) {
concurrency = MAX_SEGMENTS;
}
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
while (ssize < concurrency) {
++sshift;
ssize <<= 1;
}
segmentShift = 32 - sshift;
segmentMask = ssize - 1;
this.segments = new Segment[ssize];
try {
for (int i = 0; i < this.segments.length; ++i) {
this.segments[i] = segmentFactory.newInstance();
}
} catch (RuntimeException e) {
for (Segment, ?> s : this.segments) {
if (s != null) {
s.destroy();
}
}
throw e;
}
}
protected Segment segmentFor(Object key) {
return segmentFor(key.hashCode());
}
protected Segment segmentFor(int hash) {
return segments[getIndexFor(hash)];
}
public int getIndexFor(int hash) {
return (spread(hash) >>> segmentShift) & segmentMask;
}
public List> getSegments() {
return Collections.unmodifiableList(Arrays.asList(segments));
}
protected int getConcurrency() {
return segments.length;
}
private static int spread(int hash) {
int h = hash;
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
@Override
public int size() {
long sum = 0;
readLockAll();
try {
for (Map, ?> m : segments) {
sum += m.size();
}
} finally {
readUnlockAll();
}
if (sum > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
} else {
return (int) sum;
}
}
@Override
public boolean containsKey(Object key) {
return segmentFor(key).containsKey(key);
}
@Override
public boolean containsValue(Object value) {
// Resort to locking all segments
readLockAll();
try {
for (Map, ?> m : segments) {
if (m.containsValue(value)) {
return true;
}
}
} finally {
readUnlockAll();
}
return false;
}
@Override
public V get(Object key) {
return segmentFor(key).get(key);
}
@Override
public V put(K key, V value) {
try {
return segmentFor(key).put(key, value);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key.hashCode())) {
try {
return segmentFor(key).put(key, value);
} catch (OversizeMappingException ex) {
//ignore
}
}
writeLockAll();
try {
do {
try {
return segmentFor(key).put(key, value);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key.hashCode()));
throw e;
} finally {
writeUnlockAll();
}
}
}
public V put(K key, V value, int metadata) {
try {
return segmentFor(key).put(key, value, metadata);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key.hashCode())) {
try {
return segmentFor(key).put(key, value, metadata);
} catch (OversizeMappingException ex) {
//ignore
}
}
writeLockAll();
try {
do {
try {
return segmentFor(key).put(key, value, metadata);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key.hashCode()));
throw e;
} finally {
writeUnlockAll();
}
}
}
/**
* See {@link OffHeapHashMap#fill(Object, Object)} for a detailed description.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with key, or
* null if there was no mapping for key
* (irrespective of whether the value was successfully installed).
*/
public V fill(K key, V value) {
return segmentFor(key).fill(key, value);
}
public V fill(K key, V value, int metadata) {
return segmentFor(key).fill(key, value, metadata);
}
@Override
public V remove(Object key) {
return segmentFor(key).remove(key);
}
public boolean removeNoReturn(Object key) {
return segmentFor(key).removeNoReturn(key);
}
public Integer getMetadata(K key, int mask) throws IllegalArgumentException {
return segmentFor(key).getMetadata(key, mask);
}
public Integer getAndSetMetadata(K key, int mask, int values) throws IllegalArgumentException {
return segmentFor(key).getAndSetMetadata(key, mask, values);
}
public V getValueAndSetMetadata(K key, int mask, int values) {
return segmentFor(key).getValueAndSetMetadata(key, mask, values);
}
@Override
public void clear() {
writeLockAll();
try {
for (Map, ?> m : segments) {
m.clear();
}
} finally {
writeUnlockAll();
}
}
public void destroy() {
writeLockAll();
try {
for (Segment, ?> m : segments) {
m.destroy();
}
} finally {
writeUnlockAll();
}
}
@Override
public V putIfAbsent(K key, V value) {
try {
return segmentFor(key).putIfAbsent(key, value);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key.hashCode())) {
try {
return segmentFor(key).putIfAbsent(key, value);
} catch (OversizeMappingException ex) {
e = ex;
}
}
writeLockAll();
try {
do {
try {
return segmentFor(key).putIfAbsent(key, value);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key.hashCode()));
throw e;
} finally {
writeUnlockAll();
}
}
}
@Override
public boolean remove(Object key, Object value) {
return segmentFor(key).remove(key, value);
}
@Override
public boolean replace(K key, V oldValue, V newValue) {
try {
return segmentFor(key).replace(key, oldValue, newValue);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key.hashCode())) {
try {
return segmentFor(key).replace(key, oldValue, newValue);
} catch (OversizeMappingException ex) {
e = ex;
}
}
writeLockAll();
try {
do {
try {
return segmentFor(key).replace(key, oldValue, newValue);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key.hashCode()));
throw e;
} finally {
writeUnlockAll();
}
}
}
@Override
public V replace(K key, V value) {
try {
return segmentFor(key).replace(key, value);
} catch (OversizeMappingException e) {
if (handleOversizeMappingException(key.hashCode())) {
try {
return segmentFor(key).replace(key, value);
} catch (OversizeMappingException ex) {
e = ex;
}
}
writeLockAll();
try {
do {
try {
return segmentFor(key).replace(key, value);
} catch (OversizeMappingException ex) {
e = ex;
}
} while (handleOversizeMappingException(key.hashCode()));
throw e;
} finally {
writeUnlockAll();
}
}
}
@Override
public Set keySet() {
Set ks = keySet;
return ks != null ? ks : (keySet = new AggregateKeySet());
}
@Override
public Collection values() {
Collection vc = values;
return vc != null ? vc : (values = new AggregatedValuesCollection());
}
@Override
public Set> entrySet() {
Set> es = entrySet;
return es != null ? es : (entrySet = new AggregateEntrySet());
}
class AggregateKeySet extends BaseAggregateSet {
@Override
public boolean contains(final Object o) {
return AbstractConcurrentOffHeapMap.this.containsKey(o);
}
@Override
public boolean remove(final Object o) {
return AbstractConcurrentOffHeapMap.this.remove(o) != null;
}
@Override
public Iterator iterator() {
return new AggregateIterator() {
@Override
protected Iterator getNextIterator() {
return listIterator.next().keySet().iterator();
}
};
}
}
class AggregateEntrySet extends BaseAggregateSet> {
@Override
public Iterator> iterator() {
return new AggregateIterator>() {
@Override
protected Iterator> getNextIterator() {
return listIterator.next().entrySet().iterator();
}
};
}
@Override
public boolean contains(final Object o) {
if (!(o instanceof Entry, ?>)) { return false; }
final Map.Entry, ?> e = (Map.Entry, ?>) o;
final V value = AbstractConcurrentOffHeapMap.this.get(e.getKey());
return value != null && value.equals(e.getValue());
}
@Override
public boolean remove(final Object o) {
if (!(o instanceof Entry, ?>)) { return false; }
final Map.Entry, ?> e = (Map.Entry, ?>) o;
final V value = AbstractConcurrentOffHeapMap.this.get(e.getKey());
return value != null && value.equals(e.getValue()) && AbstractConcurrentOffHeapMap.this.remove(e.getKey()) != null;
}
}
class AggregatedValuesCollection extends AbstractCollection {
@Override
public Iterator iterator() {
return new AggregateIterator() {
@Override
protected Iterator getNextIterator() {
return listIterator.next().values().iterator();
}
};
}
@Override
public int size() {
return AbstractConcurrentOffHeapMap.this.size();
}
}
private abstract class BaseAggregateSet extends AbstractSet {
@Override
public int size() {
return AbstractConcurrentOffHeapMap.this.size();
}
@Override
public void clear() {
AbstractConcurrentOffHeapMap.this.clear();
}
}
protected abstract class AggregateIterator implements Iterator {
protected final Iterator