У меня есть поток, который работает в бесконечном цикле, выполняя некоторый код, который получит блокировку.
Тем временем у меня есть другой поток, выполняющий запланированную задачу, которой также нужно получить блокировку, чтобы что-то сделать.
Чтобы гарантировать, что запланированная задача не будет голодать, если блокировка слишком долго занята бесконечным циклом, я позволяю потоку засыпать на мгновение после разблокировки. Я думаю, что это разумный шаг, а не излишество, при условии, что я не возражаю против снижения производительности со сном?
И дополнительный вопрос: я вижу, что кто-то еще делает то же самое, но вместо того, чтобы спать в течение постоянного времени, он спит в течение случайного времени 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);
}
Некоторые моменты для уточнения и дополнительный вопрос:
writeLock.tryLock(100, TimeUnit.MILLISECONDS)
), аналогично тому, как это делает поток с бесконечным циклом.Почему бы не проверить, должна ли запланированная задача произойти в цикле в вашем вопросе, и вообще отказаться от другого потока и блокировки?
Пробовали звонить java.lang.Thread#yield
?
спасибо @AndreyB.Panfilov. yield
звучит как хорошая идея. Однако я не уверен, что это несколько недетерминировано, как я видел The thread scheduler is free to ignore yield hint
. Будет ли это проблемой?
По крайней мере, для описанного вами сценария я не вижу никакой пользы от случайного времени сна, такого как 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?
. Не могли бы вы взглянуть?
Спасибо за разъяснение. А что касается вашего дополнительного вопроса, ответ "непредсказуем". По сути, поток бесконечного цикла освобождает блокировку на наносекунду или меньше и пытается снова получить блокировку. А другие потоки выполняют tryLock(time, unit), что по сути является тем же бесконечным циклом с более точным управлением. Один из них получит замок. Если оба вызова получения происходят одновременно, хорошая JVM выделит блокировку другому потоку. Но опять же, это вопрос наносекунд или меньше. Таким образом, он может или не может получить блокировку когда-либо. Я не верю, что это хорошая идея.
Спасибо @aatwork. Я думаю, как предложил @AndreyB.Panfilov. по моему вопросу, yield
также может быть хорошим решением, если планировщик потоков не будет его игнорировать.
yield() не снимает блокировку. поэтому вам нужно использовать releaseLock() и yield(). Аналогичная проблема повторится. В основном другие потоки должны выполнять tryLock() одновременно. если они этого не сделают, первый поток снова получит блокировку. По сути, это releaseLock() без sleep(), о котором мы говорили выше. Вашим потокам нужен такой же замок. Таким образом, требуется некоторая межпоточная связь о том, когда блокировка получена и освобождена. Я считаю, что это лучше подходит для wait() и notify().
Если вы подозреваете, что в цикле «trylock...sleep» может быть более одного потока, небольшая случайность может помочь вывести их из синхронного режима.