Игнорировать потоки, если пул заполнен

#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 всегда вызывается одним и тем же потоком, что верно в вашем случае, поскольку оно вызывается обработчиком событий кнопки.)