Использование ExecutorService в параллелизме Java

Java

Использование ExecutorService в параллелизме Java

ExecutorService — это инфраструктура асинхронного выполнения на языке java. Используя ExecutorService, вы можете легко создать многопоточную среду выполнения.

В этой статье подробно объясняется конкретное использование ExecutorService.

Создать ExecutorService

Вообще говоря, существует два способа создания ExecutorService.

Первый способ — использовать методы фабричного класса в Executors, например:

ExecutorService executor = Executors.newFixedThreadPool(10);

В дополнение к методу newFixedThreadPool Executors также содержат множество методов для создания ExecutorService.

Второй метод — создать ExecutorService напрямую, поскольку ExecutorService — это интерфейс, нам нужно создать экземпляр реализации ExecutorService.

Здесь мы используем ThreadPoolExecutor в качестве примера:

ExecutorService executorService =
            new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>());

Назначение задач ExecutorService

ExecutorService может выполнять задачи Runnable и Callable. Где Runnable не имеет возвращаемого значения, а Callable имеет возвращаемое значение. Давайте рассмотрим использование двух случаев отдельно:

Runnable runnableTask = () -> {
    try {
        TimeUnit.MILLISECONDS.sleep(300);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};
 
Callable<String> callableTask = () -> {
    TimeUnit.MILLISECONDS.sleep(300);
    return "Task's execution";
};

Назначайте задачи ExecutorService, вызывая методы execute(), submit(), invokeAny(), invokeAll().

Возвращаемое значение execute() — void, которое используется для отправки задачи Runnable.

executorService.execute(runnableTask);

Возвращаемое значение submit() — Future, которое может отправить задачу Runnable или Callable. Есть два способа отправить Runnable:

<T> Future<T> submit(Runnable task, T result);

Future<?> submit(Runnable task);

Первый метод возвращает входящий результат. Второй метод возвращает ноль.

Посмотрите еще раз на использование callable:

Future<String> future = 
  executorService.submit(callableTask);

invokeAny() передает список задач executorService и возвращает успешный результат одной из них.

String result = executorService.invokeAny(callableTasks);

invokeAll() передает список задач в executorService и возвращает результаты всех успешных выполнений:

List<Future<String>> futures = executorService.invokeAll(callableTasks);

Закрыть ExecutorService

Если задачи в ExecutorService завершены, ExecutorService не будет автоматически закрыт. Он будет ждать новых заданий. Если нам нужно закрыть ExecutorService, нам нужно вызвать метод shutdown() или shutdownNow().

shutdown() немедленно уничтожит ExecutorService, что остановит ExecutorService от получения новых задач и дождется выполнения всех существующих задач перед уничтожением.

executorService.shutdown();

shutdownNow() не гарантирует, что все задачи были выполнены, она возвращает список невыполненных задач:

List<Runnable> notExecutedTasks = executorService.shutdownNow();

Лучший метод завершения работы, рекомендованный оракулом, — это использовать с awaitTermination:

executorService.shutdown();
       try {
           if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) {
               executorService.shutdownNow();
           }
       } catch (InterruptedException e) {
           executorService.shutdownNow();
       }

Сначала прекратите получение задач, а затем подождите определенный период времени, пока все задачи будут выполнены.Если заданное время превышено, задача будет немедленно прекращена.

Future

И submit(), и invokeAll() возвращают объекты Future. Мы подробно говорили о Future в предыдущей статье. Вот лишь список того, как его использовать:

Future<String> future = executorService.submit(callableTask);
String result = null;
try {
   result = future.get();
} catch (InterruptedException | ExecutionException e) {
   e.printStackTrace();
}

ScheduledExecutorService

ScheduledExecutorService предоставляет нам механизм для регулярного выполнения задач.

Мы создаем ScheduledExecutorService следующим образом:

ScheduledExecutorService executorService
                = Executors.newSingleThreadScheduledExecutor();

Метод расписания executorService может передаваться в Runnable или Callable:

Future<String> future = executorService.schedule(() -> {
        // ...
        return "Hello world";
    }, 1, TimeUnit.SECONDS);
 
    ScheduledFuture<?> scheduledFuture = executorService.schedule(() -> {
        // ...
    }, 1, TimeUnit.SECONDS);

Есть еще два аналогичных метода:

scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit )

scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit ) 

Разница между ними заключается в том, что период первого рассчитывается на основе времени начала задачи, а второй — на основе времени окончания задачи.

ExecutorService и Fork/Join

Java 7 представила инфраструктуру Fork/Join. Так в чем же разница между ними?

ExecutorService позволяет пользователям самостоятельно управлять генерируемыми потоками, обеспечивая более детальный контроль над потоками. Fork/Join позволяет ускорить выполнение задачи.

Пожалуйста, обратитесь к коду этой статьиGitHub.com/Dadean2009/приходите…

Дополнительные руководства см.блог флайдина