LINUX.ORG.RU

Правильная синхронизация set-get в Java

 ,


0

1

Юзаю findbugs для анализа написанного. Есть singleton, использование которого аналогично кэшу с небольшой предварительной обработкой, читают его все, а пишет в него poller раз в час. Поскольку читать его могут сразу несколько объектов, ставить synchronized на него как-то странно, получится узкое место (не самое крупное, но все-таки). С другой стороны, синхронизация с сеттером все-таки нужна.

сейчас сделал через ReentrantLock(), получилось так (обработку исключений, доп скобки и прочее убрал чтоб глаза не мозолить)

getter() {
  if (setterLock.isLocked())
    waittime = isSet.awaitNanos(...);
  if (waittime <= 0)
    return Collection.emptyMap();
  return instance;
}

setter(...) {
  setterLock.lock();
  try {
    ...
    if (setterLock.hasWaiters(isSet))
      isSet.signalAll();
  } finally {
    setterLock.unlock();
  }
}

findbugs ругается что мол некошерно юзать isLocked(). но другого варианта как проверить лок без собственно его захвата я что-то не нашел (tryLock() его закрывает, и надо кстати на него в сеттере поменять lock()).

★★★★★

Последнее исправление: upcFrost (всего исправлений: 2)
Ответ на: комментарий от maxcom

почему бы не использовать ReadWriteLock?

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

await не прокатит, оно будет просто ждать сигнала, а нужно чтоб await вызывался только если лок собственно закрыт

у ReentrantLock есть isLocked, но она типа некошерна и debug-only. Не, оно работает, и отрубить эту проверку не проблема, просто вдруг я оттупил

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 2)
Ответ на: комментарий от upcFrost

ReadWriteLock сам всё проверит. Когда write захвачен, то read захватить нельзя. Когда read захвачен, то write захватить нельзя, но можно ещё раз захватить read.

netrino
()

но другого варианта как проверить лок без собственно его захвата я что-то не нашел (tryLock() его закрывает, и надо кстати на него в сеттере поменять lock()).

тебе не надо «проверить лок без собственно его захвата». Тебе надо ReadWriteLock: The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.

asaw ★★★★★
()
Ответ на: комментарий от asaw

The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.

а, вот оно что. Ок, asaw, netrino, спасибо

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от Deleted

банальным AtomicReference

для кого банальным, а для кого «в первый раз вижу». но раз увидел - буду знать. то что нужно, спасибо.

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

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

если у тебя состояние коллекции меняется - то лок не поможет, нужна коллекция которая поддерживает многопоточность

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

get() {
  Map localRef;
  synchronized(lock) {
    localRef =  this.ref;
  }
  return localRef == null? Collections.emptyMap() : localRef;
}

set(Map newRef) {
// если таки надо что-то делать то делаем это вне синхронайз блока
// исключение - когда нежелательно делать это конкуррентно, но это отдельная история (и тут уже имеет смысл ReadWriteLock)
  synchronized(lock) {
// вообще в guava есть ImmutableMap... но тут делаем ее руками
    this.ref = Collections.unmodifiableMap(new HashMap(newRef));
  }
}

и все

Deleted
()
Последнее исправление: Deleted (всего исправлений: 2)
Ответ на: комментарий от Deleted

состояние меняется только поллером, для всех остальных где возможно - возвращается именно unmodifiableList/Map. Установка ссылки - копейки, но там ставится более одной ссылки (приходится один набор данных фасовать в две разные коллекции - Map и 2d-дерево, иначе сложность поиска сильно возрастает). Единственное что меня смущает - если внезапно между установкой ссылок решит проснуться GC и что-нибудь подчистить, тормознув полсистемы

synchronized(lock) {
    localRef =  this.ref;
  }

разве это не означает что геттером может воспользоваться только один поток, а остальные курят пока эта ссылка пройдет? не, копейки, но все-таки вариант с AtomicReference как-то более органично выглядит

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

ставится более одной ссылки (приходится один набор данных фасовать в две разные коллекции - Map и 2d-дерево, иначе сложность поиска сильно возрастает)

Подсказка:

MyObject{
  final Map map;
  final MyTree tree;
}

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

у тебя там лютый хайлоад или что? будет тормозить - будешь париться по оптимизации.

Deleted
()
Последнее исправление: Deleted (всего исправлений: 1)
Ответ на: комментарий от Deleted

у тебя там лютый хайлоад или что? будет тормозить - будешь париться по оптимизации

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

MyObject{
  final Map map;
  final MyTree tree;
}

ну тоже можно, в целом да, одна ссылка тогда

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 2)

по-моему в посте у тебя описана в точности стандартная ConcurrentHashMap. Рид общий, пут защищет локом. Причем она в разы круче тем, что на запись локает не весь объект, а только конкретный букет

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

stevejobs ★★★★☆
()
Последнее исправление: stevejobs (всего исправлений: 1)
Ответ на: комментарий от sanwashere

ТС просил же

вообще хз, щас я посмотрел JLS, да пишут что атомарно, получается, если ТСа устроит иногда старое значение после записи нового - то можно вообще убрать синхронайз, если таки хочется порядка то заменить на volatile.

Deleted
()
Ответ на: комментарий от sanwashere

Проблема не в присваивании, а в упорядоченности операций. Если ты в одном потоке сделаешь sharedPointer = new Data(1, 2), а в другом в это время будешь считывать sharedPointer.data1, то можешь попасть в ситуацию, когда память выделилась, указатель присвоился, но результаты выполнения конструктора не видны, хотя и кажется, что он выполнился до присваивания.

Legioner ★★★★★
()
Ответ на: комментарий от sanwashere

final емнип не гарантирует что остальные поля будут видны, для этого еcть «synchronization actions» тотже volatile

Deleted
()
Ответ на: комментарий от sanwashere

final имеет смысл только в рамках Java на этапе компиляции (т.е. запретить другим людям присваивать новое значение в эту переменную без дополнительных выкрутасов). Сама виртуальная машина на final кладёт большой болт и никак этот факт не использует ни для чего, насколько мне известно (хотя бы потому, что final-поля можно reflection-ом менять и это валидный код, хоть и дурацкий). С многопоточностью это точно не поможет. По идее поможет volatile, хотя я бы перечитал умные книжки, может и тут грабли лежат. Лучше всего AtomicReference, он должен работать.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

С многопоточностью это точно не поможет.

https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

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

Deleted
()
Ответ на: комментарий от Deleted

кто-то взял и добавил не финальное поле

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

Ну и я как-то соглашусь с пони - имхо идея про AtomicReference была самой подходящей для такого финта. Либо, если бы обработка была прямо внутри сеттера - ReadWriteLock

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

вообще по правилам хорошего тона и нормального кода, надо иметь определенные причины чтоб не делать поле final.

посмотри например как устроен спринг, ну это так чтоб не забывать каково там, в реальности

Deleted
()
Ответ на: комментарий от Deleted

посмотри например как устроен спринг, ну это так чтоб не забывать каково там, в реальности

я прямо сейчас на него смотрю. давай не будем о грустном... а то можно еще припомнить гуглоAPI, который вообще брезгует set/get чтоб чуть-чуть повысить производительность

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от Deleted

Ясно, спасибо, значит всё сложней, чем я думал.

Legioner ★★★★★
()
Ответ на: комментарий от Legioner

final влияет. JVM вставляет нужные барьеры памяти и ограничивает переупорядочивание инструкций. А уже изменения final через рефлекшыны хз как обрабатывает. Это в принципе undefined behaviour (хотя бы потому что есть политики безопасности, которые задаются извне).

dzidzitop ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.