@Override public boolean containsKey(@Nullable Object key) { if (key == null) { return false; } int hash = hash(key); return segmentFor(hash).containsKey(key, hash); }
public void testDrainKeyReferenceQueueOnWrite() { for (MapMaker maker : allWeakKeyStrengthMakers()) { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(maker.concurrencyLevel(1)); if (maker.getKeyStrength() == Strength.WEAK) { Segment<Object, Object, ?, ?> segment = map.segments[0]; Object keyOne = new Object(); int hashOne = map.hash(keyOne); Object valueOne = new Object(); Object keyTwo = new Object(); Object valueTwo = new Object(); map.put(keyOne, valueOne); InternalEntry<Object, Object, ?> entry = segment.getEntry(keyOne, hashOne); @SuppressWarnings("unchecked") Reference<Object> reference = (Reference) entry; reference.enqueue(); map.put(keyTwo, valueTwo); assertFalse(map.containsKey(keyOne)); assertFalse(map.containsValue(valueOne)); assertNull(map.get(keyOne)); assertEquals(1, map.size()); assertNull(segment.getKeyReferenceQueueForTesting().poll()); } } }
public void testSegmentPut_expand() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; assertEquals(1, segment.table.length()); int count = 1024; for (int i = 0; i < count; i++) { Object key = new Object(); Object value = new Object(); int hash = map.hash(key); assertNull(segment.put(key, hash, value, false)); assertTrue(segment.table.length() > i); } }
@Override public boolean replace(K key, @Nullable V oldValue, V newValue) { checkNotNull(key); checkNotNull(newValue); if (oldValue == null) { return false; } int hash = hash(key); return segmentFor(hash).replace(key, hash, oldValue, newValue); }
/** * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. */ @GuardedBy("Segment.this") @VisibleForTesting ReferenceEntry<K, V> copyEntry(ReferenceEntry<K, V> original, ReferenceEntry<K, V> newNext) { int hash = original.getHash(); return segmentFor(hash).copyEntry(original, newNext); }
/** * This method is a convenience for testing. Code should call {@link Segment#copyEntry} directly. */ // Guarded By Segment.this @VisibleForTesting E copyEntry(E original, E newNext) { int hash = original.getHash(); return segmentFor(hash).copyEntry(original, newNext); }
V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); try { preWriteCleanup(); expand(); newCount = this.count + 1; int index = hash & (table.length() - 1); E first = table.get(index); if (e.getHash() == hash && entryKey != null && map.keyEquivalence.equivalent(key, entryKey)) { setValue(e, value); newCount = this.count; // count remains unchanged this.count = newCount; // write-volatile setValue(e, value); return entryValue; E newEntry = map.entryHelper.newEntry(self(), key, hash, first); setValue(newEntry, value); table.set(index, newEntry); this.count = newCount; // write-volatile return null; } finally { unlock();
void expand() { AtomicReferenceArray<E> oldTable = table; int oldCapacity = oldTable.length(); if (oldCapacity >= MAXIMUM_CAPACITY) { return; AtomicReferenceArray<E> newTable = newEntryArray(oldCapacity << 1); threshold = newTable.length() * 3 / 4; int newMask = newTable.length() - 1; for (int oldIndex = 0; oldIndex < oldCapacity; ++oldIndex) { int newIndex = e.getHash() & newMask; E newNext = newTable.get(newIndex); E newFirst = copyEntry(e, newNext); if (newFirst != null) { newTable.set(newIndex, newFirst);
public void testClear() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).initialCapacity(1)); Segment<Object, Object, ?, ?> segment = map.segments[0]; AtomicReferenceArray<? extends InternalEntry<Object, Object, ?>> table = segment.table; assertEquals(1, table.length()); Object key = new Object(); Object value = new Object(); int hash = map.hash(key); InternalEntry<Object, Object, ?> entry = segment.newEntryForTesting(key, hash, null); segment.setValueForTesting(entry, value); segment.setTableEntryForTesting(0, entry); segment.readCount.incrementAndGet(); segment.count = 1; assertSame(entry, table.get(0)); segment.clear(); assertNull(table.get(0)); assertEquals(0, segment.readCount.get()); assertEquals(0, segment.count); }
/** * Removes an entry from within a table. All entries following the removed node can stay, but * all preceding ones need to be cloned. * * <p>This method does not decrement count for the removed entry, but does decrement count for * all partially collected entries which are skipped. As such callers which are modifying count * must re-read it after calling removeFromChain. * * @param first the first entry of the table * @param entry the entry being removed from the table * @return the new first entry for the table */ @GuardedBy("Segment.this") ReferenceEntry<K, V> removeFromChain(ReferenceEntry<K, V> first, ReferenceEntry<K, V> entry) { evictionQueue.remove(entry); expirationQueue.remove(entry); int newCount = count; ReferenceEntry<K, V> newFirst = entry.getNext(); for (ReferenceEntry<K, V> e = first; e != entry; e = e.getNext()) { ReferenceEntry<K, V> next = copyEntry(e, newFirst); if (next != null) { newFirst = next; } else { removeCollectedEntry(e); newCount--; } } this.count = newCount; return newFirst; }
@CanIgnoreReturnValue @Override public V replace(K key, V value) { checkNotNull(key); checkNotNull(value); int hash = hash(key); return segmentFor(hash).replace(key, hash, value); }
E getEntry(Object key, int hash) { if (count != 0) { // read-volatile for (E e = getFirst(hash); e != null; e = e.getNext()) { if (e.getHash() != hash) { continue; } K entryKey = e.getKey(); if (entryKey == null) { tryDrainReferenceQueues(); continue; } if (map.keyEquivalence.equivalent(key, entryKey)) { return e; } } } return null; }
public void testSegmentPut() { MapMakerInternalMap<Object, Object, ?, ?> map = makeMap(createMapMaker().concurrencyLevel(1).weakValues()); Segment<Object, Object, ?, ?> segment = map.segments[0]; // TODO(fry): check recency ordering Object key = new Object(); int hash = map.hash(key); Object oldValue = new Object(); Object newValue = new Object(); // no entry assertEquals(0, segment.count); assertNull(segment.put(key, hash, oldValue, false)); assertEquals(1, segment.count); // same key assertSame(oldValue, segment.put(key, hash, newValue, false)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); // cleared InternalEntry<Object, Object, ?> entry = segment.getEntry(key, hash); WeakValueReference<Object, Object, ?> oldValueRef = segment.newWeakValueReferenceForTesting(entry, oldValue); segment.setWeakValueReferenceForTesting(entry, oldValueRef); assertSame(oldValue, segment.get(key, hash)); oldValueRef.clear(); assertNull(segment.put(key, hash, newValue, false)); assertEquals(1, segment.count); assertSame(newValue, segment.get(key, hash)); }
@Override public void clear() { for (Segment<K, V, E, S> segment : segments) { segment.clear(); } }
E getLiveEntry(Object key, int hash) { return getEntry(key, hash); }
@Override public void clear() { for (Segment<K, V, E, S> segment : segments) { segment.clear(); } }
/** * Removes an entry from within a table. All entries following the removed node can stay, but * all preceding ones need to be cloned. * * <p>This method does not decrement count for the removed entry, but does decrement count for * all partially collected entries which are skipped. As such callers which are modifying count * must re-read it after calling removeFromChain. * * @param first the first entry of the table * @param entry the entry being removed from the table * @return the new first entry for the table */ @GuardedBy("this") E removeFromChain(E first, E entry) { int newCount = count; E newFirst = entry.getNext(); for (E e = first; e != entry; e = e.getNext()) { E next = copyEntry(e, newFirst); if (next != null) { newFirst = next; } else { newCount--; } } this.count = newCount; return newFirst; }
@Override public void clear() { for (Segment<K, V, E, S> segment : segments) { segment.clear(); } }
/** * Removes an entry from within a table. All entries following the removed node can stay, but * all preceding ones need to be cloned. * * <p>This method does not decrement count for the removed entry, but does decrement count for * all partially collected entries which are skipped. As such callers which are modifying count * must re-read it after calling removeFromChain. * * @param first the first entry of the table * @param entry the entry being removed from the table * @return the new first entry for the table */ @GuardedBy("this") E removeFromChain(E first, E entry) { int newCount = count; E newFirst = entry.getNext(); for (E e = first; e != entry; e = e.getNext()) { E next = copyEntry(e, newFirst); if (next != null) { newFirst = next; } else { newCount--; } } this.count = newCount; return newFirst; }
/** * Removes an entry from within a table. All entries following the removed node can stay, but * all preceding ones need to be cloned. * * <p>This method does not decrement count for the removed entry, but does decrement count for * all partially collected entries which are skipped. As such callers which are modifying count * must re-read it after calling removeFromChain. * * @param first the first entry of the table * @param entry the entry being removed from the table * @return the new first entry for the table */ @GuardedBy("this") E removeFromChain(E first, E entry) { int newCount = count; E newFirst = entry.getNext(); for (E e = first; e != entry; e = e.getNext()) { E next = copyEntry(e, newFirst); if (next != null) { newFirst = next; } else { newCount--; } } this.count = newCount; return newFirst; }