Избегайте использования флага отключения в многопоточном коде - PullRequest
0 голосов
/ 10 мая 2019

У меня есть следующий код:

private static final AtomicBoolean shutdown = new AtomicBoolean(false);

public static void main(final String... args) {

    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        shutdown.set(true);
        executorService.shutdown();
        try {
            executorService.awaitTermination(SHUTDOWN_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
        } catch (InterruptedException e) {
               executorService.shutdownNow();
        }
    }));

    executorService = Executors.newFixedThreadPool(2);
    for (int i = 0; i < 2; i++) {
        executorService.execute(create());
    }
}

private static Runnable create() {
    return new Runnable() {
        @Override
        public void run() {
            while (!shutdown.get()) {
                try {
                    Thread.sleep(5000);
                    System.out.println("Hatella" + Thread.currentThread().getName());
                } catch (Throwable t) {
                }
            }
        }
    };
}

Этот код работает отлично, но я хотел написать этот код гораздо более простым способом, чтобы мне не приходилось проверять состояние флага отключения в каждомпока цикл.Любая идея, что я могу сделать, чтобы исправить это и добиться того же.

Ответы [ 2 ]

1 голос
/ 10 мая 2019

shutdown() только заставит ExecutorService не принимать больше задач, но продолжит выполнение всех ожидающих задач до конца.Поскольку вы на самом деле хотите прекратить выполнение задач, вы должны в первую очередь использовать shutdownNow(), который будет посылать сигнал прерывания.

public static void main(final String... args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);

    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        executorService.shutdownNow();
        try {
            executorService.awaitTermination(
                SHUTDOWN_TIMEOUT.getSeconds(),TimeUnit.SECONDS);
        } catch (InterruptedException e) {}
    }));

    for (int i = 0; i < 2; i++) {
        executorService.execute(create());
    }
}

private static Runnable create() {
    return () -> {
        while(!Thread.interrupted()) {
            try {
                Thread.sleep(5000);
                System.out.println("Hatella" + Thread.currentThread().getName());
            }
            catch(InterruptedException ex) {
                break;
            }
            catch (Throwable t) {
            }
        }
        System.out.println("thread exit " + Thread.currentThread().getName());
    };
}

Флаг прерывания можно запрашивать не только через Thread.interrupted(), онтакже сделает блокирующие действия, такие как Thread.sleep(…), прекращением ранее, сообщая о ситуации через InterruptedException.В обоих случаях, когда Thread.interrupted() вернул true или когда был выдан InterruptedException, статус прерывания будет сброшен, поэтому важно либо немедленно отреагировать на него, либо помнить, что вы его получили.Таким образом, в приведенном выше примере catch(InterruptedException ex) содержит break для завершения цикла.

Но, как показано, прерывание не прерывает поток, а позволяет реагировать на него, например, путем очистки при необходимости,перед выходом.

Обратите внимание, что, когда единственными длительными операциями являются блокирующие, вам вообще не нужно опрашивать статус прерывания вручную, например, будет работать также следующее:

private static Runnable create() {
    return () -> {
        while(true) {
            try {
                Thread.sleep(5000);
                System.out.println("Hatella" + Thread.currentThread().getName());
            }
            catch(InterruptedException ex) {
                System.out.println("got "+ex+", "+Thread.interrupted());
                break;
            }
            catch (Throwable t) {
            }
        }
        System.out.println("thread exit");
    };
}

Поскольку этот код не проверяет и не сбрасывает прерванное состояние с помощью Thread.interrupted(), сигнал будет сохраняться до следующего вызова Thread.sleep, что будет достаточно скоро, чтобы появиться в качестве немедленного ответа, поскольку код выполняется между двумяsleep звонки короткие.

0 голосов
/ 10 мая 2019

A) См. Превращение ExecutorService в демон в Java .Потоки демона технически ответят на поставленный вопрос (не требуется опрашивать переменную «shutdown»), но, вероятно, являются идеей bad в любом контексте с состоянием, поскольку поток будет остановлен в середине операции без предупреждения со стороныJVM (как только завершатся все потоки, не являющиеся демонами).

executorService = Executors.newFixedThreadPool(2, r -> {
            Thread t = Executors.defaultThreadFactory().newThread();
            t.setDaemon(true);
            return t;
        });

B) Еще один вариант в реальном мире (где незанятый поток, вероятно, блокирует / спит на чем-то) - это проверять только shutdownна InterruptedException, который произойдет на executorService.shutdownNow()

...