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

brave.internal.collect.WeakConcurrentMap Maven / Gradle / Ivy

There is a newer version: 6.0.3
Show newest version
/*
 * Copyright 2013-2020 The OpenZipkin Authors
 *
 * 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 brave.internal.collect;

import brave.internal.Nullable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.AbstractMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * This borrows heavily from Rafael Winterhalter's {@code com.blogspot.mydailyjava.weaklockfree.WeakConcurrentMap}
 * with the following major changes:
 *
 * 

The biggest change is this removes LatentKey. Instead it relies on keys known to have a * stable {@link #hashCode} and who are {@linkplain #equals(Object) equal to} a weak reference of * itself. We allow lookups using externally created contexts, yet don't want to incur overhead of * key allocation or classloader problems sharing keys with a thread local. * *

Other changes mostly remove features (to reduce the bytecode size) and address style: *

    *
  • Inline expunction only as we have no thread to use anyway
  • *
  • Stylistic changes including different javadoc and removal of private modifiers
  • *
  • toString: derived only from keys
  • *
* *

See https://github.com/raphw/weak-lock-free */ public class WeakConcurrentMap extends ReferenceQueue implements Iterable> { final ConcurrentMap, V> target = new ConcurrentHashMap<>(); @Nullable public V getIfPresent(K key) { if (key == null) throw new NullPointerException("key == null"); expungeStaleEntries(); return target.get(key); } /** Replaces the entry with the indicated key and returns the old value or {@code null}. */ @Nullable public V putIfProbablyAbsent(K key, V value) { if (key == null) throw new NullPointerException("key == null"); if (value == null) throw new NullPointerException("value == null"); expungeStaleEntries(); return target.putIfAbsent(new WeakKey<>(key, this), value); } /** Removes the entry with the indicated key and returns the old value or {@code null}. */ @Nullable public V remove(K key) { if (key == null) throw new NullPointerException("key == null"); expungeStaleEntries(); return target.remove(key); } /** Iterates over the entries in this map. */ @Override public Iterator> iterator() { return new EntryIterator(target.entrySet().iterator()); } /** Cleans all unused references. */ protected void expungeStaleEntries() { Reference reference; while ((reference = poll()) != null) { removeStaleEntry(reference); } } protected V removeStaleEntry(Reference reference) { return target.remove(reference); } // This comment was directly verbatim from https://github.com/raphw/weak-lock-free/blob/dcbd2fa0d30571bb3ed187a42cb75323a5569d5b/src/main/java/com/blogspot/mydailyjava/weaklockfree/WeakConcurrentMap.java#L273-L302 /* * Why this works: * --------------- * * Note that this map only supports reference equality for keys and uses system hash codes. Also, for the * WeakKey instances to function correctly, we are voluntarily breaking the Java API contract for * hashCode/equals of these instances. * * * System hash codes are immutable and can therefore be computed prematurely and are stored explicitly * within the WeakKey instances. This way, we always know the correct hash code of a key and always * end up in the correct bucket of our target map. This remains true even after the weakly referenced * key is collected. * * If we are looking up the value of the current key via WeakConcurrentMap::get or any other public * API method, we know that any value associated with this key must still be in the map as the mere * existence of this key makes it ineligible for garbage collection. Therefore, looking up a value * using another WeakKey wrapper guarantees a correct result. * * If we are looking up the map entry of a WeakKey after polling it from the reference queue, we know * that the actual key was already collected and calling WeakKey::get returns null for both the polled * instance and the instance within the map. Since we explicitly stored the identity hash code for the * referenced value, it is however trivial to identify the correct bucket. From this bucket, the first * weak key with a null reference is removed. Due to hash collision, we do not know if this entry * represents the weak key. However, we do know that the reference queue polls at least as many weak * keys as there are stale map entries within the target map. If no key is ever removed from the map * explicitly, the reference queue eventually polls exactly as many weak keys as there are stale entries. * * Therefore, we can guarantee that there is no memory leak. */ static final class WeakKey extends WeakReference { final int hashCode; WeakKey(T key, ReferenceQueue queue) { super(key, queue); this.hashCode = key.hashCode(); // cache as hashCode is used for all future operations } @Override public int hashCode() { return hashCode; } @Override public String toString() { T value = get(); return value != null ? value.toString() : "ClearedReference()"; } /** * While a lookup key will invoke equals against this, the visa versa is not true. This method * is only used inside the target map to resolve hash code collisions. */ @Override public boolean equals(Object o) { // resolves hash code collisions if (o == this) return true; assert o instanceof WeakReference : "Bug: unexpected input to equals"; return equal(get(), ((WeakReference) o).get()); } } @Override public String toString() { Class thisClass = getClass(); while (thisClass.getSimpleName().isEmpty()) { thisClass = thisClass.getSuperclass(); } expungeStaleEntries(); // Clean up so that only present references show up (unless race lost) return thisClass.getSimpleName() + target.keySet(); } static boolean equal(@Nullable Object a, @Nullable Object b) { return a == null ? b == null : a.equals(b); // Java 6 can't use Objects.equals() } class EntryIterator implements Iterator> { private final Iterator, V>> iterator; private Map.Entry, V> nextEntry; private K nextKey; private EntryIterator(Iterator, V>> iterator) { this.iterator = iterator; findNext(); } private void findNext() { while (iterator.hasNext()) { nextEntry = iterator.next(); nextKey = nextEntry.getKey().get(); if (nextKey != null) { return; } } nextEntry = null; nextKey = null; } @Override public boolean hasNext() { return nextKey != null; } @Override public Map.Entry next() { if (nextKey == null) { throw new NoSuchElementException(); } try { return new AbstractMap.SimpleImmutableEntry(nextKey, nextEntry.getValue()); } finally { findNext(); } } @Override public void remove() { throw new UnsupportedOperationException(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy