#java #multithreading
#java #многопоточность
Вопрос:
Я хочу добиться следующего поведения в моем приложении Java:
При нажатии кнопки задача вызывается в новом потоке. Однако, если кнопка нажата снова, а текущая задача все еще выполняется, я не хочу запускать другую.
Я не уверен, какой хороший подход для этого. В настоящее время я использую код, аналогичный приведенному ниже.
private ExecutorService backgroundTaskExecutor = Executors.newSingleThreadExecutor();
private boolean runningBackgroundTask = false;
private synchronized void setRunningBackgroundTask(boolean running) {
this.runningBackgroundTask = running;
}
private synchronized boolean isRunningBackgroundTask() {
return runningBackgroundTask;
}
private void runBackgroundTask() {
if (isRunningBackgroundTask()) {
System.out.println("This task will be ignored because the thread pool is busy.");
return;
}
Runnable backgroundTask = new Runnable() {
@Override
public void run() {
setRunningBackgroundTask(true);
// Do something that takes time...
setRunningBackgroundTask(false);
}
};
backgroundTaskExecutor.submit(backgroundTask);
}
Это хорошая практика? Есть ли в Java какая-либо утилита для этого?
Ответ №1:
С вашим текущим кодом вы можете в конечном итоге иметь более одной задачи в очереди исполнителя одновременно, потому что вы проверяете, выполняется ли задача в начале вашего метода, но устанавливаете для нее значение true только внутри runnable. Вполне возможно, что вы могли бы вызвать метод дважды, прежде чем будет запущена первая строка runnable и состояние будет установлено в true.
Чтобы этого не произошло, вы могли бы использовать an AtomicBoolean
для получения и установки состояния атомарно.
private final AtomicBoolean runningBackgroundTask = new AtomicBoolean(false);
private void runBackgroundTask() {
if (runningBackgroundTask.getAndSet(true)) {
System.out.println("This task will be ignored because the thread pool is busy.");
return;
}
Runnable backgroundTask = new Runnable() {
@Override
public void run() {
// Do something that takes time...
runningBackgroundTask.set(false);
}
};
backgroundTaskExecutor.submit(backgroundTask);
}
Комментарии:
1. Хорошее решение, другое решение будет выполнять как проверку, так и запуск потока внутри одного и того же синхронизированного блока.
2. Интересно, я впервые слышу об этом. Это кажется более безопасным, чем проверка флага в синхронизированном методе.
3. Ваше решение содержит гонку данных: поток пула может потерять свой временной интервал после выполнения «чего-то, что требует времени», но до установки флага. Таким образом, существует промежуток времени, в течение которого щелчок мыши игнорируется, даже если в этот момент ничего не происходит. Это может создать иллюзию, что приложение нестабильно (иногда вы нажимаете, и ничего не происходит), или нет. Вероятно, это зависит от того, как завершение задачи синхронизируется с отображением. Если на дисплее есть время показать завершение до того, как будет принят новый щелчок, то у вас может возникнуть проблема.
4. @jameslarge Решением этой проблемы было бы не отображать, что задача завершена до установки флага. Программа должна вести себя так, как будто задача завершается в то же время, когда для флага установлено значение false.
Ответ №2:
Хотя решение, опубликованное Алексом, в порядке, я хотел бы предоставить альтернативу, которая использует Future
. Его можно использовать, когда вы хотите запустить any (alien) Runnable
, потому что ему не обязательно «знать» о runningBackgroundTask
флаге:
private final ExecutorService executor = Executors.newSingleThreadExecutor();
private Future<?> resu<
private void runInBackground(Runnable task) {
if (result != null amp;amp; !result.isDone()) {
// This task will be ignored because the thread pool is busy
return;
}
result = executor.submit(task);
}
(Обратите внимание, что result
это не синхронизировано, поэтому это работает только в том случае, если runInBackground
всегда вызывается одним и тем же потоком, что верно в вашем случае, поскольку оно вызывается обработчиком событий кнопки.)