Java SoftReference странное поведение - PullRequest
0 голосов
/ 27 декабря 2018
Map<E, SoftReference<T>> cache = new ConcurrentHashMap<E, SoftReference<T>>();

У меня есть карта, объявленная как та, что приведена выше, которую я использую в качестве Кэша.

Проблема в том, что я выполняю все операции с Кешем сразу после добавления элемента вКэш, но не позднее.

Например:

cache.add("Username", "Tom");

if(cache.contains("Username")) возвращает значение true, но

String userName = (String)cache.get("Username") возвращает значение NULL.

Это происходит только через долгое время.

Если я получу значение после нескольких часов добавления его в кэш, я получу значение правильно.Если я получу значение через долгое время, скажем, более 15-20 часов, я получу ноль.

Когда GC очистит объекты SoftReference, ключ останется в HashMap?Это причина такого поведения?

Ответы [ 3 ]

0 голосов
/ 27 декабря 2018

Согласно oracle docs

Все мягкие ссылки на объекты с мягким доступом гарантированно будут очищены до того, как виртуальная машина сгенерирует ошибку OutOfMemoryError.

Да Когда GC очищает объекты SoftReference, ключ остается в HashMap.Ключ и соответствующее значение не имеют никакого отношения, кроме случаев, когда они находятся внутри карты.Сделайте map's value нормальной ссылкой, и они всегда будут на карте, если карта не является GC.

0 голосов
/ 07 января 2019

Когда референт SoftReference получает мусор, SoftReference получает очищенный , т. Е. Его поле референта устанавливается на null.

Так что не толькоПри нажатии клавиши key на карте соответствующее значение, экземпляр SoftReference, также остается на карте, даже если его референтное поле равно null.

Но поскольку между объявленным * 1012 должен быть слой* field и вызывающая сторона cache.add("Username", "Tom") и (String)cache.get("Username"), которые вы не показывали, возможно, даже этот слой обрабатывает его правильно.

Для полноты правильная реализация может выглядеть как

final Map<E, SoftReference<T>> cache = new ConcurrentHashMap<>();

/** remove all cleared references */
private void clean() {
    cache.values().removeIf(r -> r.get() == null);
}
public void add(E key, T value) {
    clean();
    cache.put(key, new SoftReference<>(value));
}
public boolean contains(E key) {
    clean();
    return cache.computeIfPresent(key, (e,r) -> r.get()==null? null: r) != null;
}
public T get(E key) {
    clean();
    for(;;) {
        SoftReference<T> ref = cache.computeIfPresent(key, (e,r) -> r.get()==null? null: r);
        if(ref == null) return null;
        T value = ref.get();
        if(value != null) return value;
    }
}

Этот код гарантирует, что сопоставления собранных значений будут удалены атомарно при запросе.Кроме того, хотя и не требуется, операция clean() удаляет все собранные записи на карте, чтобы уменьшить пространство на карте (сравнимо с тем, как WeakHashMap работает внутри).

Но обратите внимание, что гарантии все еще нетчто когда cache.contains("Username") возвращает true, последующее cache.get("Username") вернет не-1027 * значение.Эта проблема также известна как анти-шаблон check-then-act , который может завершиться ошибкой, поскольку между проверкой и последующим действием может происходить параллельное обновление (здесь, даже если вы используете кэштолько один поток, так как сборка мусора может происходить асинхронно), устарел результат предыдущего теста.

В этом отношении операция contains бесполезна для большинства сценариев.Вы должны вызвать get, чтобы получить сильную ссылку на значение, и продолжить работу с этой ссылкой, если не- null.

0 голосов
/ 27 декабря 2018

Да, это нормальное поведение.

SoftReference является сборщиком мусора, в результате чего значение на карте устанавливается равным null .

Это то же самое, что установка значения определенного ключа на ноль для других типов карт (например, Карта)

...