ConcurrentHashMap: удалить при условии - PullRequest
2 голосов
/ 23 апреля 2019

У меня есть ConcurrentHashMap, который используется в качестве хранилища в памяти (или кеша, можно сказать)

Чего я хотел бы добиться: одновременно проверить, «готов» ли элемент, и, если да, удалить его с карты (+ вернуть его вызывающей стороне). Нет прямого метода, который позволил бы мне сделать это.

Единственное решение, которое я придумал, - это ItemContainer, который будет содержать как элемент, так и метаданные (поле isReady). При каждом доступе мне придется применять операции merge или compute. По сути замена контейнера объекта при каждом доступе / проверке.

Вопросы:

  1. Мое решение кажется разумным?
  2. Есть ли хорошие библиотеки, которые бы достигли чего-то подобного?

Я добавил «шаблонный» код в соответствии с запросом:

public class Main {

    public static void main(String[] args) {
        Storage storage = new Storage();
        storage.put("1", new Record("s", 100));
        storage.put("2", new Record("s", 4));
        storage.removeIf("1", Main::isReady);
    }

    public static boolean isReady(Record record) {
        return record.i > 42;
    }

    public static class Record {

        public Record(String s, Integer i) {
            this.s = s;
            this.i = i;
        }

        String s;
        Integer i;
    }

    public static class Storage {
        ConcurrentHashMap<String, Record> storage = new ConcurrentHashMap<>();

        public void put(String key, Record record) {
            storage.put(key, record);
        }

        public Record removeIf(String key, Function<Record, Boolean> condition) {
            return null; // TODO: implement
        }
    }
}

Другие решения (с компромиссами):

  1. Всегда remove() на чеке, а затем merge() обратно на карту.
  2. Используйте кэш с разумной политикой эвакуации предметов (т.е. LRU) и проверяйте только эвакуированные предметы.

На основе решения @ernest_k:

public Record removeIf(String key, Predicate<Record> condition) {
    AtomicReference<Record> existing = new AtomicReference<>();

    this.storage.computeIfPresent(key, (k, v) -> {
        boolean conditionSatisfied = condition.test(v);

        if (conditionSatisfied) {
            existing.set(v);
            return null;
        } else {
            existing.set(null);
            return v;
        }
    });

    return existing.get();
}

1 Ответ

3 голосов
/ 23 апреля 2019

ConcurrentHashMap уже дает вам гарантию атомарности с computeIfPresent.

Если присутствует значение для указанного ключа, попытка вычислить новое отображение, учитывая ключ и его текущее отображениезначение.Весь вызов метода выполняется атомарно.Некоторые попытки обновления этой карты другими потоками могут быть заблокированы во время вычислений, поэтому вычисления должны быть короткими и простыми и не должны пытаться обновить какие-либо другие сопоставления этой карты.

Таким образом, вы можете просто использовать это:

public Record removeIf(String key, Predicate<Record> condition) {

    AtomicReference<Record> existing = new AtomicReference<>();

    this.storage.computeIfPresent(key, (k, v) -> {
        existing.set(v);
        return condition.test(v) ? null : v;
    });

    return existing.get();
}

Обратите внимание, что я использовал Predicate<Record>, поскольку это должно быть предпочтительнее Function<Record, Boolean>.

Причина сохранения текущего значения в AtomicReference здесь нужно убедиться, что возвращаемое значение совпадает с тем, с которым был протестирован предикат (в противном случае может быть условие гонки).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...