Подтвердить что ты не робот

Синхронизация по целочисленному значению

Возможный дубликат:
Каков наилучший способ увеличить количество блокировок в java.

Предположим, что я хочу заблокировать на основе целочисленного значения id. В этом случае есть функция, которая извлекает значение из кеша и делает довольно дорогое извлечение/сохранение в кеш, если значение не существует.

Существующий код не синхронизирован и может запускать несколько операций извлечения/хранения:

//psuedocode
public Page getPage (Integer id){
   Page p = cache.get(id);
   if (p==null)
   {
      p=getFromDataBase(id);
      cache.store(p);
   }
}

То, что я хотел бы сделать, это синхронизировать извлечение по идентификатору, например.

   if (p==null)
   {
       synchronized (id)
       {
        ..retrieve, store
       }
   }

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

Есть ли простой способ убедиться, что у вас есть один и тот же экземпляр Integer? Например, будет ли это работать:

 syncrhonized (Integer.valueOf(id.intValue())){

javadoc для Integer.valueOf(), похоже, подразумевает, что вы, вероятно, получите тот же экземпляр, но это не похоже на гарантию:

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

Итак, любые предложения о том, как получить экземпляр Integer, который гарантированно будет таким же, кроме более сложных решений, таких как сохранение объектов WeakHashMap Lock, привязанных к int? (ничего страшного в этом, кажется, что должен быть очевидный однострочный, чем мне не хватает).

4b9b3361

Ответ 1

Вы действительно не хотите синхронизировать с Integer, так как у вас нет контроля над тем, какие экземпляры одинаковы и какие экземпляры разные. Java просто не предоставляет такого средства (если вы не используете целые числа в небольшом диапазоне), который зависит от разных JVM. Если вам действительно нужно синхронизировать Integer, вам нужно сохранить карту или набор целых чисел, чтобы вы могли гарантировать, что вы получите точный экземпляр, который вы хотите.

Лучше было бы создать новый объект, возможно, сохраненный в HashMap с ключом Integer для синхронизации. Что-то вроде этого:

public Page getPage(Integer id) {
  Page p = cache.get(id);
  if (p == null) {
    synchronized (getCacheSyncObject(id)) {
      p = getFromDataBase(id);
      cache.store(p);
    }
  }
}

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>();

private Object getCacheSyncObject(final Integer id) {
  locks.putIfAbsent(id, id);
  return locks.get(id);
}

Чтобы объяснить этот код, он использует ConcurrentMap, что позволяет использовать putIfAbsent. Вы можете сделать это:

  locks.putIfAbsent(id, new Object());

но затем вы берете (небольшую) стоимость создания объекта для каждого доступа. Чтобы этого избежать, я просто сохраняю сам Integer в Map. Что это значит? Почему это не отличается от использования самого Integer?

Когда вы выполняете get() с Map, ключи сравниваются с equals() (или, по крайней мере, используемый метод эквивалентен использованию equals()). Два разных экземпляра Integer одинакового значения будут равны друг другу. Таким образом, вы можете передать любое количество различных экземпляров Integer "new Integer(5)" в качестве параметра getCacheSyncObject, и вы всегда получите только тот самый первый экземпляр, который был передан в том, что содержало это значение.

Есть причины, по которым вы не можете синхронизировать на Integer... вы можете попасть в тупиковые ситуации, если несколько потоков синхронизируются в объектах Integer и, таким образом, невольно используют одни и те же блокировки, когда они хотят использовать разные блокировки, Вы можете устранить этот риск, используя

  locks.putIfAbsent(id, new Object());

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

Ответ 2

Используйте безопасную для потоков карту, например ConcurrentHashMap. Это позволит вам безопасно манипулировать картой, но для выполнения реальных вычислений используйте другую блокировку. Таким образом, вы можете выполнять несколько вычислений одновременно с одной картой.

Используйте ConcurrentMap.putIfAbsent, но вместо того, чтобы поместить фактическое значение, вместо этого используйте Future с конструкцией с вычислением света. Возможно реализация FutureTask. Запустите вычисление, а затем get результат, который будет блокировать потоки до завершения.

Ответ 3

Integer.valueOf() возвращает только кешированные экземпляры для ограниченного диапазона. Вы не указали свой диапазон, но в целом это не сработает.

Однако я бы настоятельно рекомендовал вам не применять этот подход, даже если ваши значения находятся в правильном диапазоне. Поскольку эти кэшированные экземпляры Integer доступны для любого кода, вы не можете полностью контролировать синхронизацию, что может привести к тупиковой ситуации. Это та самая проблема, с которой люди пытаются заблокировать результат String.intern().

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

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

Ответ 4

Использование синхронизированного в Integer звука действительно неправильно по дизайну.

Если вам нужно синхронизировать каждый элемент отдельно только во время извлечения/хранения, вы можете создать набор и сохранить там заблокированные элементы. Другими словами,

// this contains only those IDs that are currently locked, that is, this
// will contain only very few IDs most of the time
Set<Integer> activeIds = ...

Object retrieve(Integer id) {
    // acquire "lock" on item #id
    synchronized(activeIds) {
        while(activeIds.contains(id)) {
            try { 
                activeIds.wait();   
            } catch(InterruptedExcption e){...}
        }
        activeIds.add(id);
    }
    try {

        // do the retrieve here...

        return value;

    } finally {
        // release lock on item #id
        synchronized(activeIds) { 
            activeIds.remove(id); 
            activeIds.notifyAll(); 
        }   
    }   
}

То же самое касается магазина.

Суть в том, что нет единственной строки кода, которая решает эту проблему точно так, как вам нужно.

Ответ 5

Как о ConcurrentHashMap с объектами Integer как ключи?

Ответ 6

Вы можете посмотреть этот код для создания мьютекса из идентификатора. Код был написан для String ID, но может быть легко отредактирован для объектов Integer.

Ответ 8

Как вы можете видеть из множества ответов, существуют различные способы скрыть эту кошку:

  • Goetz et al. подход к сохранению кеша FutureTasks работает достаточно хорошо в таких ситуациях, когда вы "кэшируете что-то в любом случае", поэтому не против создания карты объектов FutureTask (и если вы не обращаете внимания на рост карты, по крайней мере, легко сделать его обрезкой параллельно)
  • В качестве общего ответа на вопрос "как заблокировать идентификатор" подход, описанный Антонио, имеет то преимущество, что он становится очевидным, когда карта блокировок добавляется/удаляется из.

Возможно, вам нужно будет учесть потенциальную проблему с реализацией Antonio, а именно, что notifyAll() будет запускать потоки, ожидающие всех идентификаторов, когда один из них станет доступным, что может не очень хорошо масштабироваться под высоким разрешением. В принципе, я думаю, вы можете исправить это, имея объект Condition для каждого заблокированного в настоящее время идентификатора, который затем является тем, что вы ожидаете/сигнализируете. Разумеется, если на практике в любой момент времени ожидается более одного идентификатора, то это не проблема.

Ответ 9

Стив,

ваш предлагаемый код имеет кучу проблем с синхронизацией. (Антонио тоже делает).

Подводя итог:

  • Вам нужно кэшировать дорогой объект.
  • Вам нужно убедиться, что, хотя один поток выполняет поиск, другой поток также не пытается извлечь один и тот же объект.
  • То, что для n-потоков все попытки получить объект только 1 объект всегда извлекается и возвращается.
  • Это для потоков, запрашивающих разные объекты, которые они не конфликтуют друг с другом.

псевдокод, чтобы это произошло (используя ConcurrentHashMap в качестве кеша):

ConcurrentMap<Integer, java.util.concurrent.Future<Page>> cache = new ConcurrentHashMap<Integer, java.util.concurrent.Future<Page>>;

public Page getPage(Integer id) {
    Future<Page> myFuture = new Future<Page>();
    cache.putIfAbsent(id, myFuture);
    Future<Page> actualFuture = cache.get(id);
    if ( actualFuture == myFuture ) {
        // I am the first w00t!
        Page page = getFromDataBase(id);
        myFuture.set(page);
    }
    return actualFuture.get();
}

Примечание:

  • java.util.concurrent.Future - это интерфейс
  • java.util.concurrent.Future на самом деле не имеет set(), но посмотрите на существующие классы, которые реализуют Future, чтобы понять, как реализовать свое собственное будущее (или использовать FutureTask).
  • Нажатие фактического поиска на рабочий поток почти наверняка будет хорошей идеей.