Спите поток, чтобы избежать голодания потока

У меня есть поток, который работает в бесконечном цикле, выполняя некоторый код, который получит блокировку. Тем временем у меня есть другой поток, выполняющий запланированную задачу, которой также нужно получить блокировку, чтобы что-то сделать. Чтобы гарантировать, что запланированная задача не будет голодать, если блокировка слишком долго занята бесконечным циклом, я позволяю потоку засыпать на мгновение после разблокировки. Я думаю, что это разумный шаг, а не излишество, при условии, что я не возражаю против снижения производительности со сном?
И дополнительный вопрос: я вижу, что кто-то еще делает то же самое, но вместо того, чтобы спать в течение постоянного времени, он спит в течение случайного времени ThreadUtil.sleep(RandomUtil.nextInt(5, 100)), но я не мог понять преимущества этого.

private final Lock writeLock = new ReentrantLock(true);

while (true) {
  writeLock.tryLock(100, TimeUnit.MILLISECONDS);

  try {
    doSomething();
  } finally {
    writeLock.unlock();
  }
  
  // food for everyone!
  ThreadUtil.sleep(10);
}

Некоторые моменты для уточнения и дополнительный вопрос:

  1. Запланированная задача получает блокировку с тайм-аутом (writeLock.tryLock(100, TimeUnit.MILLISECONDS)), аналогично тому, как это делает поток с бесконечным циклом.
  2. В дополнение к запланированной задаче на самом деле есть еще один вариант использования, чтобы запустить функцию вручную (через вызов ws) и получить блокировку, чтобы что-то сделать.
  3. Вопрос: если поток бесконечного цикла вообще НЕ спит, я предполагаю, что другие потоки в конечном итоге все равно будут выполняться, просто может быть задержка на неопределенное время?

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

access violation 21.11.2022 03:03

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

tgdavies 21.11.2022 03:25

Пробовали звонить java.lang.Thread#yield?

Andrey B. Panfilov 21.11.2022 05:16

спасибо @AndreyB.Panfilov. yield звучит как хорошая идея. Однако я не уверен, что это несколько недетерминировано, как я видел The thread scheduler is free to ignore yield hint. Будет ли это проблемой?

waynewingsyd 21.11.2022 06:14
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
64
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

По крайней мере, для описанного вами сценария я не вижу никакой пользы от случайного времени сна, такого как ThreadUtil.sleep(RandomUtil.nextInt(5, 100)). Ваш ThreadUtil.sleep(10); будет делать ту же работу.

Единственное, я не уверен, как запланированный вами поток задач получает блокировку. Я думаю, что у него также будет цикл, который проверяет каждые x мс (скажем, 10 мс), снята ли блокировка или нет. Пока у вас есть только два потока (один с блокировкой, а другой пытается получить блокировку), все должно быть в порядке. Но допустим, у нас есть три потока. Поток имеет блокировку, а потоки B и C выполняют цикл для получения блокировки. Если поток B всегда выходит и пытается выполнить перед потоком C, и блокировка снимается потоком A в этот интервал, поток C всегда будет голодать. Цикл сна и освобождения всегда будет происходить только между потоками A и B, поскольку расписание предсказуемо. Чтобы сделать это непредсказуемым и убедиться, что все три потока получат возможность получить блокировку, необходима некоторая случайность во время сна.

Но я думаю, что есть лучшее решение, чем спать и приобретать. wait() и notifyAll() здесь подошли бы лучше. Поток A вызывает notify() для некоторого объекта после определенного интервала и начинает его ждать. Потоки B и C уже ожидают один и тот же объект. Один из них будет уведомлен и получит блокировку. Допустим, на этот раз C получил замок. Если C не имеет какой-либо запланированной задачи, он снова немедленно вызовет notify(). Тогда один из A или B получит замок. Кто получит блокировку, будет решать JVM, и у нее будет лучшая реализация, чтобы один поток не голодал.

спасибо @aatwork Мне тоже нравится ваше решение с ожиданием-уведомлением, особенно если я не могу позволить себе спать 10 мс в цикле, так как я думаю, что это обеспечит лучшую пропускную способность. Я уточнил ваш вопрос о how scheduled task thread is acquiring the lock и дополнительный вопрос if the infinite loop thread does NOT sleep at all, I assume other threads will still eventually be executed, just that there might be a delay of uncertain amount of time?. Не могли бы вы взглянуть?

waynewingsyd 21.11.2022 04:05

Спасибо за разъяснение. А что касается вашего дополнительного вопроса, ответ "непредсказуем". По сути, поток бесконечного цикла освобождает блокировку на наносекунду или меньше и пытается снова получить блокировку. А другие потоки выполняют tryLock(time, unit), что по сути является тем же бесконечным циклом с более точным управлением. Один из них получит замок. Если оба вызова получения происходят одновременно, хорошая JVM выделит блокировку другому потоку. Но опять же, это вопрос наносекунд или меньше. Таким образом, он может или не может получить блокировку когда-либо. Я не верю, что это хорошая идея.

aatwork 21.11.2022 05:39

Спасибо @aatwork. Я думаю, как предложил @AndreyB.Panfilov. по моему вопросу, yield также может быть хорошим решением, если планировщик потоков не будет его игнорировать.

waynewingsyd 21.11.2022 06:16

yield() не снимает блокировку. поэтому вам нужно использовать releaseLock() и yield(). Аналогичная проблема повторится. В основном другие потоки должны выполнять tryLock() одновременно. если они этого не сделают, первый поток снова получит блокировку. По сути, это releaseLock() без sleep(), о котором мы говорили выше. Вашим потокам нужен такой же замок. Таким образом, требуется некоторая межпоточная связь о том, когда блокировка получена и освобождена. Я считаю, что это лучше подходит для wait() и notify().

aatwork 21.11.2022 06:37

Другие вопросы по теме