Придирчивый хозяин (блокировка?)
Я считаю, что мою проблему можно рассматривать независимо от используемого языка, но, чтобы иметь некоторый "якорь", я опишу ее с помощью языка Java.
Давайте рассмотрим следующий сценарий: у меня есть класс PickyHost, расширяющий Thread, и его экземпляр, pickyHostInst, работает. Этот класс может выглядеть так:
class PickyHost extends Thread {
private ArrayList<Guest> guests;
public void enter(Guest g) {
// deal with g
}
private void pickGuests() {
// ...
}
public void run() {
// listen indefinitely
}
}
Более того, в фоновом режиме у меня запущено много экземпляров Guest (они также расширяют класс Thread), и время от времени некоторые гости хотят вызывать метод enter для pickyHostInst с аргументом g, являющимся самим собой. Теперь я хочу, чтобы PickyHost был привередлив в следующем смысле:
Сразу после того, как кто-то вызывает метод ввода, он ставит g в конец списка гостей и заставляет g ждать уведомления. Кроме того (я думаю, в этом вся суть вопроса), он сам засыпает на 5 секунд и каким-то образом позволяет (в течение этих 5 секунд) другим гостям вызывать метод ввода (если так происходит, то он забывает о том, как долго он спал). и сбрасывает будильник ровно на 5 секунд снова) - я назову это чувствительным сном.
Как вы можете видеть, общее количество времени, в течение которого pickyHostInst спит, может быть огромным, если приходит много гостей, например: A прибывает, затем через 4 секунды B прибывает, затем еще через 4 секунды C прибывает и так далее. Однако предположим, что была создана цепочка A, B, ..., G гостей, и с момента прибытия G до 5 секунд никто не прибыл. Затем я хочу, чтобы pickyHostInst вызывал метод pickGhest, который, используя некоторый алгоритм, определяет подмножество S из {A, B, ..., G} гостей, чтобы уведомить, что они могут прекратить ждать и продолжать делать то, что они обычно делают, и, кроме того, удалить элементы S из списка гостей. Выполнение метода pickGhest может занять некоторое время, и в это время мог бы прийти какой-нибудь гость H и вызвать enter - тогда enter должен продолжаться в обычном режиме, но pickGhest должен игнорировать H и завершить свою последнюю сделку вызова с {A, B, ..., G} - не с {A, B, ..., G, H}. После окончания pickGhest следует выбрать pickyHostInst (здесь у меня есть 2 идеи - реализация любого из них сделает меня счастливым:)).
- снова погрузитесь в 5 секунд чувствительного сна, после чего, если после H не появилось ни одного гостя, снова вызовите pickGhest или
- одновременно обслуживает гостей с помощью метода enter как обычно, но вызывает pickGhest только после max("момент, когда последний гость из S (из последнего вызова) уведомляет pickyHostInst (например: последнее" Спасибо, господин хост "из числа S)", " через 5 секунд после того, как последний (самый новый) гость вызвал вход ").
Наконец, после долгого вступления мой вопрос - какие инструменты мне нужны для выполнения такой задачи? К сожалению, я немного потерян среди богатства различных блокировок и механизмов многопоточности / блокировки и не могу понять, какой из них подходит к моей проблеме (или какие, каким-то образом, объединены).
Я буду очень признателен за некоторые наброски кода, которые поставят меня на правильный путь.
2 ответа
Вы можете использовать java.util.Timer
объект, который можно сбросить в enter
метод. Задача таймера будет запущена в своем собственном потоке и сделает выбор за вас, если он не был отменен до этого.
Обратите внимание, что enter
Метод будет работать на одном из многих Guest
потоки. Это означает, что он, вероятно, должен быть синхронизирован. Самый простой способ сделать это - добавить synchronized
ключевое слово для объявления метода в Java: public synchronized void enter(Guest g)
, Это гарантирует, что только один гость может войти одновременно. Вы можете ввести код отмены / перезапуска таймера здесь.
Путь java.util.Timer
работает через абстрактный java.util.TimerTask
учебный класс. Это тип Runnable
у этого также есть метод, чтобы отменить задачу. Моя рекомендация состоит в том, чтобы запланировать задачу, которая будет выбирать гостей после интервала 5000 мсек, когда гость входит. Если задание от предыдущего гостя выполняется, сначала отмените его.
enter
Метод должен получить гостевую блокировку (используя синхронизированный блок) и ждать гостя. Сбор должен вызывать notify()
метод на гостей, которых вы выбираете. Это позволит им продолжить выполнение.
Когда вы удаляете выбранных гостей из своей очереди, имейте в виду, что коллекции Java по умолчанию не поддерживают потоки. Вам нужно будет использовать внешнюю блокировку, чтобы никто не изменил ваш список при добавлении и удалении гостей. Collections.synchronizedList(List)
Метод предоставляет удобный способ сделать это.
Вот список ссылок, которые обсуждают темы, которые я упомянул:
Я мог бы сделать это так Я бы постарался избежать notify
/notifyAll
поскольку вам придется задействовать флаг из-за ложных пробуждений, и это немного загромождает код. CountDownLatch
ИМО здесь намного лучше, хотя название немного странное.
static final long five_sec = TimeUnit.SECOND.toNanos(5)
final Queue<Pair<Guest, CountDownLatch>> guests = new LinkedList<>();
long earliest = -1;
// Synchronizing on "this" is fine but using private lock
// object is even better
final Object lock = new Object();
void enter(Guest g){
Pair p = Pair.of(g, new CountDownLatch(1));
synchronized(lock){
guests.get().add(p);
earliest = System.nanoTime() + five_sec;
}
p.second.await();
}
void pickGuests(){
synchronized(lock){
// pop a few guests from sofar and wake them
Guest g = sofar.poll();
if(g != null){
g.second.countDown();
}
}
}
void run(){
while(!Thread.currentThread().isInterrupted()){
long sleepTime;
synchronized(lock){
if(System.nanoTime() > earliest){
pickGuests();
}
sleepTime = earliest - System.nanoTime();
sleepTime = sleepTime < five_sec ? five_sec : sleepTime;
}
Thread.sleep(sleepTime);
}
}