ExecutorService в Java

Язык программирования Java очень эффективно работает с многопоточными приложениями, которые требуют, чтобы задачи выполнялись одновременно в потоке. Любому приложению становится сложно одновременно выполнять большое количество потоков. Итак, чтобы преодолеть эту проблему, Java поставляется с ExecutorService, который является подчиненным интерфейсом платформы Executors.

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

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

  • Создание потоков: он предоставляет множество методов для создания потоков, которые помогают в одновременном запуске ваших приложений.
  • Управление потоками: он также управляет жизненным циклом потока. Вам не нужно беспокоиться о том, активен ли поток, занят или мертв, прежде чем отправлять задачу на выполнение.
  • Отправка и выполнение задач: платформа Executor предоставляет методы для отправки задач в пуле потоков, а также дает возможность решать, будет ли поток выполняться или нет.

executorservice-executorservice

Пример

Это субинтерфейс структуры исполнителя, который добавляет определенные функции для управления жизненным циклом потока приложения. Он также предоставляет метод submit(), который может принимать как запускаемые, так и вызываемые объекты.

В следующем примере мы создадим ExecutorService с одним потоком, а затем отправим задачу для выполнения внутри потока.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Example {
public static void main(String[] args) {
System.out.println(" Inside : " + Thread.currentThread().getName());
System.out.println("creating ExecutorService");
ExecutorService executorservice = Executors.newSingleThreadExecutor();
System.out.println("creating a runnable");
Runnable runnable =() -> {
System.out.println("inside: "+ Thread.currentThread().getName());
};
System.out.println("submit the task specified by the runnable to the executorservice");
executorservice.submit(runnable);
}
}
Output: Inside: main
        creating ExecutorService
        creating a runnable
        submit the task specified by the runnable to the executorservice
        inside: pool-1-thread-1

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

Когда вы запустите указанную выше программу, она никогда не завершится. Вам нужно будет отключить ее явно, поскольку служба исполнителя продолжает прослушивать новые задачи.

Реализации

ExecutorService очень похож на пул потоков. Фактически, реализация ExecutorService в пакете java.util.concurrent является реализацией пула потоков. ExecutorService имеет следующие реализации в пакете java.util.concurrent:

ThreadPoolExecutor

ThreadPoolExecutor выполняет указанные задачи, используя один из своих внутренних потоков пула.
ThreadPoolExecutor
Создание threadPoolExecutor

int corePoolSize = 5;
int maxPoolSize = 10;
long keepAliveTime = 5000;
ExecutorService threadPoolExecutor = 
new threadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());

ScheduledThreadPoolExecutor

Java.util.concurrent.ScheduledThreadPoolExecutor — это ExecutorService, который может планировать выполнение задач после задержки или повторное выполнение с фиксированным интервалом времени между каждым выполнением.

ScheduledExecutorService  scheduledexecutorservice = Executors.newScheduledThreadPool (5);
ScheduledFuture scheduledfuture = scheduledExecutorService.schedule(new Callable(){
public Object call() throws Exception{
System.out.println("executed");
return "called";
}
},
5,
TimeUnit.SECONDS);

Использование

Есть несколько разных способов делегировать задачи ExecutorService.

  • execute(Runnable);
  • submit(Runnable);
  • invokeAny();
  • invokeAll();

execute(Runnable)

Выполнение ExecutorService execute(Runnable) принимает объект java.lang.Runnable и выполняет его асинхронно.

ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable(){
public void run(){
System.out.println("asynchronous task");
}
});
executorService.shutdown();

Невозможно получить результат выполнения Runnable, для этого вам нужно использовать Callable.

submit(Runnable)

Метод Java ExecutorService submit(Runnable) принимает реализацию Runnable и возвращает будущий объект. Будущий объект можно использовать для проверки завершения выполнения Runnable.

Future future = executorService.submit(new Runnable(){
public void run(){
System.out.println(:asynchronous task");
}
});
future.get(); //returns null if the task is finished correctly.

submit(Callable)

Метод submit(Callable) Java ExecutorService аналогичен submit(Runnable), но для него используется Java Callable вместо Runnable.

Future future = executorService.submit(new Callable(){
public Object call() throws Exception{
System.out.println("Asynchronous callable");
return "Callable Result";
}
});
System.out.println("future.get() = " future.get());
Output: Asynchroous callable
        future.get = Callable Result

invokeAny()

Метод invokeAny() принимает коллекцию вызываемых объектов. Вызов этого метода не возвращает будущего, но возвращает результат одного из вызываемых объектов.

ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>(){
public String call() throws Exception{
return"task A";
}
});
callables.add(new Callable<String>(){
public String call() throws Exception{
return"task B";
}
});
callables.add(new Callable<String>(){
public String call() throws Exception{
return"task C";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

Когда вы запустите приведенный выше код, результат изменится. Это может быть задача A, задача B и так далее.

InvokeAll()

Метод invokeAll() вызывает все вызываемые объекты, переданные в качестве параметров. Он возвращает будущие объекты, которые можно использовать для получения результатов выполнения каждого вызываемого объекта.

ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>(){
public String call() throws Exception{
return "Task A";
}
});
callables.add(new Callable<String>(){
public String call() throws Exception{
return "Task B";
}
});
callables.add(new Callable<String>(){
public String call() throws Exception{
return "Task C";
}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future: futures){
System.out.println(" future.get = " + future.get());
}
executorService.shutdown();

Runnable vs Callable

Запускаемый и вызываемый интерфейсы очень похожи друг на друга. Разница видна в объявлении интерфейсов. Оба интерфейса представляют собой задачу, которая может выполняться одновременно потоком или ExecutorService.

Вызываемое объявление:

public interface Callable{
public object call() throws Exception;
}

Выполняемое объявление:

public interface Runnable{
public void run();
}

Основное различие между ними заключается в том, что метод call() может возвращать объект из вызова метода. И метод call() может вызвать исключение, а метод run() — нет.

Как отменить задачу?

Вы можете отменить задачу, отправленную в ExecutorService, просто вызвав метод отмены в будущем, отправленном при отправке задачи.

future.cancel();

Завершение

Чтобы потоки не запускались даже после завершения выполнения, следует закрыть ExecutorService. Чтобы завершить потоки внутри ExecutorService, вы можете вызвать метод shutdown().

executorService.shutdown();

Оцените статью