Синхронизация HashMap для Map с приращением значения - PullRequest
4 голосов
/ 02 марта 2012

У меня есть вопросы по поводу синхронизации HashMap.Предпосылкой является то, что я пытаюсь реализовать простой способ обнаружения грубой силы.Я буду использовать карту, которая имеет имя пользователя в качестве ключа и используется для сохранения количества неудачных попыток входа пользователя в систему.В случае сбоя входа в систему я хочу сделать что-то вроде этого:

    Integer failedAmount = myMap.get("username");
    if (failedAmount == null) {
        myMap.put("username", 1);
    } else {
        failedAmount++;
        if (failedAmount >= THRESHOLD) {
            // possible brute force detected! alert admin / slow down login
            // / or whatever
        }
        myMap.put("username", failedAmount);
    }

Механизм, который я имею в виду на данный момент, довольно прост: я бы просто отслеживал это на весь день и очищал () HashMapв полночь или что-то в этом роде.

, поэтому мой вопрос: какую наилучшую / самую быструю реализацию Map я могу использовать для этого?Нужна ли мне полностью синхронизированная карта (Collections.sychronizedMap ()) или достаточно ConcurrentHashMap?Или, может быть, просто обычный HashMap?Я полагаю, это не такая уж большая проблема, если проскочить несколько шагов?

Ответы [ 5 ]

5 голосов
/ 02 марта 2012

Я бы использовал комбинацию ConcurrentHashMap и AtomicInteger http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html.

Использование AtomicInteger не поможет вам в сравнении, но поможет вам сохранить точность чисел - нет необходимости выполнять ++ и пут в двух шагах.

На ConcurrentHashMap я бы использовал метод putIfAbsent, который устранит ваше первое if условие.

AtomicInteger failedAmount = new AtomicInteger(0);

failedAmount = myMap.putIfAbsent("username", failedAmount);

if (failedAmount.incrementAndGet() >= THRESHOLD) {
    // possible brute force detected! alert admin / slow down login
    // / or whatever
}
3 голосов
/ 02 марта 2012

Если вы не синхронизируете весь блок кода, который выполняет обновление, оно все равно не будет работать так, как вы ожидаете.

Синхронизированная карта просто гарантирует, что ничего страшного не произойдет, если вы позвоните, скажем, put несколько раз одновременно Это не гарантирует, что

myMap.put("username", myMap.get("username") + 1);

выполняется атомарно.

Вы должны действительно синхронизировать весь блок, который выполняет обновление. Либо с использованием некоторого Semaphore, либо с помощью ключевого слова synchronized. Например:

final Object lock = new Object();

...

synchronized(lock) {

    if (!myMap.containsKey(username))
        myMap.put(username, 0);

    myMap.put(username, myMap.get(username) + 1);

    if (myMap.get(username) >= THRESHOLD) {
        // possible brute force detected! alert admin / slow down login
    }
}
2 голосов
/ 02 марта 2012

Лучший способ, который я вижу, - хранить счетчик сбоев вместе с пользовательским объектом, а не в какой-то глобальной карте. Таким образом, проблема синхронизации даже не появляется.

Если вы все еще хотите пойти с картой, вы можете уйти с частично синхронизированным подходом, если вы используете изменяемый объект-счетчик:

static class FailCount {
    public int count;
}

// increment counter for user
FailCount count;
synchronized (lock) {
    count = theMap.get(user);
    if (count == null) {
        count = new FailCount();
        theMap.put(user, count);
    }
}
count.count++;

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

1 голос
/ 02 марта 2012

Использование синхронизированных HashMap или ConcurrentHashMap необходимо только в том случае, если ваше приложение Monitoring является многопоточным.Если это так, ConcurrentHashMap имеет значительно лучшую производительность в случаях высокой нагрузки / конкуренции.

Я бы не посмел использовать несинхронизированную структуру с несколькими потоками, если даже один модуль записи / обновленияпоток существует.Это не просто вопрос потери нескольких приращений - внутренняя структура самого HashMap может быть повреждена.

При этом, если вы хотите убедиться, что приращение не потеряно, то даже синхронизированный Map недостаточно:

  • UserX пытается войти в систему
  • Поток A получает счетчик N для "UserX"
  • UserX пытается войти в систему снова
  • Нить B получает количество N для "UserX"
  • A ставит N + 1 на карту
  • B ставит N + 1 на карту
  • карта теперь содержит N + 1 вместо N + 2

Чтобы избежать этого, либо используйте синхронизированный блок для всей операции get / set, либо используйте что-то вроде AtomicInterer вместо простого Integer для вашего счетчика.

1 голос
/ 02 марта 2012

Самое простое решение, которое я вижу здесь, состоит в том, чтобы извлечь этот код в отдельную функцию и выполнить синхронизацию.(или поместите весь ваш код в синхронизированный блок).Все остальное осталось без изменений.Переменная карты должна быть окончательной.

...