WeakHashMap как найти запись _actually_ после того, как ссылка помещена в ReferenceQueue - PullRequest
2 голосов
/ 10 марта 2020

A WeakHashMap работает в значительной степени как WeakReference в сочетании с ReferenceQueue - об этом новостей нет. Вот урезанный пример того, как он должен работать:

public class ReferenceQueuePlayground {

    public static void main(String[] args) {
        ReferenceQueue<Referent> q = new ReferenceQueue<>();

        Referent ref = new Referent();
        WeakReference<Referent> weak = new WeakReference<>(ref, q);

        ref = null;

        // wait for GC to reclaim Referent
        while (weak.get() != null) {
            System.gc();
        }

        // this might return null, because ReferenceQueue is notified asynchronously
        // but I am assuming the happy path here 
        Reference<? extends Referent> reference = q.poll();

        // this will be false
        System.out.println(reference == null);
        // this will be true
        System.out.println(reference.get() == null);
    }

    @RequiredArgsConstructor
    @Getter
    static class Referent {

    }
}

Именно так работает WeakHashMap - он получает уведомление, когда referent исправлен и надет reference ReferenceQueue. При некоторой последующей операции вызывается expungeStaleEntries, который в основном будет брать элементы из этого ReferenceQueue один за другим и воздействовать на них.

Проблема, с которой я столкнулся: как она может "воздействовать на них", если referent сейчас нет? В конце концов, это ...HASHMap, поэтому, чтобы удалить элемент, он должен знать, что это hashCode. Как вы можете узнать hashCode для чего-то, что сейчас ушло?

1 Ответ

2 голосов
/ 10 марта 2020

Есть два пути. Первым будет линейный поиск .

Поскольку референт действительно пропал, и вы не можете вычислить hashCode на нем, вы можете искать Reference с помощью ==, по all записей в Map. В опубликованном примере вы можете добавить несколько строк, например:

    WeakReference<Referent> weak = new WeakReference<>(ref, q);
    // <--- this
    System.out.println(weak);

    ref = null;

    while (weak.get() != null) {
        System.out.println("not yet");
        System.gc();
    }

    Reference<? extends Referent> reference = q.poll();
    // <---- and this
    System.out.println(reference);

. Они обе будут печатать одну и ту же вещь, что вполне логично. Таким образом, теоретически, WeakHashMap может взять reference, который он получил (что на самом деле Entry), и пройти его внутренний массив, пока не будет найдено совпадение.

Очевидно, что это будет медленно.

Второй подход - это тот, который WeakHashMap фактически принимает . Когда Entry создается впервые, он вычисляет hashCode и помещает его в локальное поле:

/**
  * Creates new entry.
  */
Entry(Object key, V value,
      ReferenceQueue<Object> queue,
      int hash, Entry<K,V> next) {
    super(key, queue);
    this.value = value;
    this.hash  = hash;
    this.next  = next;
}

В этот момент он знает Key, поэтому он может вычислить hashCode , Когда позже вызывается expungeStaleEntries:

 private void expungeStaleEntries() {
    for (Object x; (x = queue.poll()) != null; ) {
        synchronized (queue) {
            @SuppressWarnings("unchecked")
                Entry<K,V> e = (Entry<K,V>) x;
            int i = indexFor(e.hash, table.length);

Он уже знает hashCode, так как он был вычислен до этого. Он не знает Key, но в этом также нет необходимости.

Это поможет найти корзину, в которой будет находиться эта запись, но на самом деле найти указанную запись c, она может используйте == только для самой записи. поскольку Key ушло, equals невозможно, но это не имеет значения.

...