Цикл событий в Java
На самом деле я пытаюсь написать цикл обработки событий для Nashorn (Java 8), чтобы обратные вызовы от асинхронных операций (потоки, которые я запускаю, например, для подключения к удаленным службам или выполнения длительных вычислений), были помещены в очередь и выполнены в последовательности (не параллельно). Я делаю это, помещая функции обратного вызова в ConcurrentLinkedQueue и используя ScheduledExecutorService в качестве цикла, который проверяет очередь на выполнение обратного вызова.
Работает нормально, но мои вопросы:
1) какой короткий интервал я могу использовать, не перетаскивая свой процессор? У меня их будет несколько, и они должны быть независимы друг от друга. Таким образом, может быть 50 потоков, каждый из которых выполняет свой собственный "цикл обработки событий". Этот исполнитель пытается запустить мой runnable каждые 10 мс, например....
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(<cbRunnable>, 0, 10, TimeUnit.MILLISECONDS);
2) Есть ли у этого подхода преимущество перед:
while (true) {
// check queue and execute any callback...
}
3) Есть ли лучший способ?
1 ответ
Ответ полностью зависит от того, что вы делаете внутри этого блока:
while (true) {
// check queue and execute any callback...
}
Если очередь проверяет блокировку до тех пор, пока элемент не станет доступен, то это "самый эффективный" способ опроса с точки зрения использования ЦП. Если проверка не блокируется, то вызывающий поток будет вращаться, и вы будете запускать один аппаратный поток на полную мощность на время цикла - однако это устраняет затраты на синхронизацию и даст вам абсолютное наилучшее время отклика. Вот некоторые примеры:
Очередь блокировки (наименьшая нагрузка на процессор, но за счет синхронизации)
Queue<T> q = new LinkedBlockingQueue<>();
...
while(true){
T t = q.take();
//t will never be null, do something with it here.
}
Неблокирующая очередь (большая часть нагрузки на процессор, но без затрат на синхронизацию)
Queue<T> q = new LinkedList<>();
...
while(true){
T t;
while((t = q.poll()) == null); //busy polling!
//t will never be null, do something with it here
}
ScheduledExecutorService
Наконец... если вы в конечном итоге используете запланированный сервис executor, вы заставляете некоторое ненулевое время ожидания между опросами. Это будет почти столь же эффективно с точки зрения использования ЦП по сравнению с полноценной реализацией Blocking Queue, но вы также вынуждены снижать время отклика вплоть до интервала планирования. То, что "лучше" для вашего приложения, будет зависеть от того, можете ли вы позволить себе подождать минимальное время ожидания между опросами (я думаю, что вы можете запланировать сокращение микросекунды...?), Или если вам нужно что-то более быстрое, например, занятый опрос схема.