Java: Синхронизация на примитивах?

В нашей системе у нас есть метод, который сделает некоторую работу, когда это назовут с определенным идентификатором:

public void doWork(long id) { /* ... */ }

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

Простое решение состояло бы в том, чтобы иметь Карту, которая отображается от Длинного идентификатора до некоторого произвольного объекта, который мы можем соединить. Одна проблема, которую я предвижу с этим, состоит в том, что у нас могут быть тонны идентификаторов в системе, и эта карта будет продолжать расти каждый день.

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

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

16
задан Kevin 17 February 2010 в 16:05
поделиться

10 ответов

Некоторое время назад я изобрел для себя нечто подобное. Я называю это блокировкой класса эквивалентности, то есть блокирует все вещи, которые равны данной вещи. Вы можете получить его из моего github и использовать его в соответствии с лицензией Apache 2, если хотите, или просто прочтите и забудьте!

15
ответ дан 30 November 2019 в 16:58
поделиться

Вы можете попробовать что-нибудь с ReentrantLock, например, у вас будет Map . Теперь после lock.release () вы можете протестировать lock.hasQueuedThreads (). Если это вернет false, вы можете удалить его с карты.

8
ответ дан 30 November 2019 в 16:58
поделиться

Для начала:

  1. Вы не можете заблокировать примитив и
  2. Не блокируйте Long, если вы не следите за тем, как вы их создаете. Длинные значения, созданные автобоксингом или Long.valueOf() в определенном диапазоне, гарантированно одинаковы в JVM, что означает, что другие потоки могут блокировать тот же самый объект Long и давать вам перекрестные помехи. Это может быть тонкой ошибкой параллелизма (подобно блокировке на intern'ed строках).

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

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

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

Есть также некоторые материалы по этому вопросу и пример StripedMap в Java Concurrency in Practice раздел 11.4.3.

4
ответ дан 30 November 2019 в 16:58
поделиться

Не достаточно ли использовать SynchronizedHashMap или Collections. synchronizedMap(Map m) из пакета java.util.concurrent вместо обычного HashMap, где вызовы извлечения и вставки не синхронизированы?

что-то вроде:

Map<Long,Object> myMap = new HashMap<Long,Object>();
Map<Long,Object> mySyncedMap=Collections.synchronizedMap(myMap);
0
ответ дан 30 November 2019 в 16:58
поделиться

Преждевременная оптимизация - это корень зла

Попробуйте использовать (синхронизированную) карту.

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

0
ответ дан 30 November 2019 в 16:58
поделиться

Убедитесь, что ни один столбец не содержит значений null в связанной таблице (т. е. в обновляемой таблице).

-121--956765-

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

Не является серьезным предложением для использования в производстве, но, вероятно, единственный способ на самом деле получить правильный ответ: (.

-121--3926368-

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

После создания карты канонизации необходимо записать код, защищенный блокировкой:

Long lockObject = canonMap.get(id);
synchronized (lockObject)
{
    // stuff
}

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

0
ответ дан 30 November 2019 в 16:58
поделиться

Вы можете попробовать следующий небольшой «прием»

String str = UNIQUE_METHOD_PREFIX + Long.toString(id);
synchornized(str.intern()) { .. }

, который со 100% гарантией вернет тот же самый экземпляр.

UNIQUE_METHOD_PREFIX может быть жестко запрограммированной константой или может быть получен с помощью:

StackTraceElement ste = Thread.currentThread().getStackTrace()[0];
String uniquePrefix = ste.getDeclaringClass() + ":" +ste.getMethodName();

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

7
ответ дан 30 November 2019 в 16:58
поделиться

Я бы сказал, что вы уже довольно далеки от решения. Создайте LockManager , который лениво и со счетчиком ссылок управляет этими блокировками за вас. Затем используйте его в doWork :

public void doWork(long id) {
    LockObject lock = lockManager.GetMonitor(id);
    try {
        synchronized(lock) {
            // ...
        }
    } finally {
        lock.Release();
    }
}
4
ответ дан 30 November 2019 в 16:58
поделиться

Я предлагаю вам использовать утилиты из java.util.concurrent, особенно класс AtomicLong. См. связанную javadoc

-3
ответ дан 30 November 2019 в 16:58
поделиться

Вы можете создать список или набор активных идентификаторов и использовать wait и notify:

List<Long> working;
public void doWork(long id) {
synchronized(working)
{
   while(working.contains(id))
   {
      working.wait();
   }
   working.add(id)//lock
}
//do something
synchronized(working)
{
    working.remove(id);//unlock
    working.notifyAll();
}
}

Решенные проблемы:

  • Только потоки с одинаковым идентификатором ждут, все остальные работают одновременно
  • Нет утечки памяти, так как "блокировки" (длинные) будут удалены при разблокировке
  • Работает с автобоксингом

Проблемы:

  • while / notifyAll может вызвать некоторую потерю производительности при большом количестве потоков
  • Не реентерабельность
0
ответ дан 30 November 2019 в 16:58
поделиться
Другие вопросы по тегам:

Похожие вопросы: