Рубрики
Без рубрики

Асинхронное программирование на Java

Узнайте о нескольких способах асинхронного программирования на Java

Автор оригинала: baeldung.

1. Обзор

В связи с растущим спросом на написание неблокирующего кода нам нужны способы асинхронного выполнения кода.

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

2. Асинхронное программирование на Java

2.1. Нить

Мы можем создать новый поток для асинхронного выполнения любой операции. С выпуском лямбда-выражений в Java 8 он стал более чистым и читаемым.

Давайте создадим новый поток, который вычисляет и выводит факториал числа:

int number = 20;
Thread newThread = new Thread(() -> {
    System.out.println("Factorial of " + number + " is: " + factorial(number));
});
newThread.start();

2.2. Задача на будущее

Начиная с Java 5, интерфейс Future предоставляет способ выполнения асинхронных операций с использованием FutureTask .

Мы можем использовать submit метод ExecutorService для асинхронного выполнения задачи и возврата экземпляра FutureTask .

Итак, давайте найдем факториал числа:

ExecutorService threadpool = Executors.newCachedThreadPool();
Future futureTask = threadpool.submit(() -> factorial(number));

while (!futureTask.isDone()) {
    System.out.println("FutureTask is not finished yet..."); 
} 
long result = futureTask.get(); 

threadpool.shutdown();

Здесь мы использовали метод isDone , предоставленный интерфейсом Future , чтобы проверить, завершена ли задача. После завершения мы можем получить результат с помощью метода get .

2.3. Полное будущее

Java 8 представила Завершаемое будущее с сочетанием Будущего и Стадии завершения . Он предоставляет различные методы, такие как supplyAsync , RunAsync и thenApplyAsync для асинхронного программирования.

Итак, давайте используем CompletableFuture вместо FutureTask , чтобы найти факториал числа:

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> factorial(number));
while (!completableFuture.isDone()) {
    System.out.println("CompletableFuture is not finished yet...");
}
long result = completableFuture.get();

Нам не нужно явно использовать ExecutorService . CompletableFuture внутренне использует ForkJoinPool для асинхронной обработки задачи . Следовательно, это делает наш код намного чище.

3. Гуава

Guava предоставляет класс ListenableFuture для выполнения асинхронных операций.

Во-первых, мы добавим последнюю версию guava зависимость Maven:


    com.google.guava
    guava
    28.2-jre

Затем давайте найдем факториал числа, используя ListenableFuture :

ExecutorService threadpool = Executors.newCachedThreadPool();
ListeningExecutorService service = MoreExecutors.listeningDecorator(threadpool);
ListenableFuture guavaFuture = (ListenableFuture) service.submit(()-> factorial(number));
long result = guavaFuture.get();

Здесь класс Дополнительные исполнители предоставляет экземпляр класса ListeningExecutorService . Затем метод ListeningExecutorService.submit выполняет задачу асинхронно и возвращает экземпляр ListenableFuture .

Гуава также обладает Будущее класс, предоставляющий такие методы, как Синхронизация отправки , расписание Асинхронное , и преобразование Асинхронно чтобы приковать Прослушиваемое будущее похоже на Полное будущее.

Например, давайте посмотрим, как использовать Futures.submitAsync вместо метода ListeningExecutorService.submit :

ListeningExecutorService service = MoreExecutors.listeningDecorator(threadpool);
AsyncCallable asyncCallable = Callables.asAsyncCallable(new Callable() {
    public Long call() {
        return factorial(number);
    }
}, service);
ListenableFuture guavaFuture = Futures.submitAsync(asyncCallable, service);

Здесь метод submit Async требует аргумента AsyncCallable , который создается с помощью Вызываемого класса.

Кроме того, класс Futures предоставляет метод addCallback для регистрации обратных вызовов успеха и сбоя:

Futures.addCallback(
  factorialFuture,
  new FutureCallback() {
      public void onSuccess(Long factorial) {
          System.out.println(factorial);
      }
      public void onFailure(Throwable thrown) {
          thrown.getCause();
      }
  }, 
  service);

4. Асинхронность Советника

Electronic Arts внедрила функцию асинхронного ожидания от .СЕТЬ в экосистему Java через советник-асинхронный библиотека .

Библиотека позволяет последовательно писать асинхронный (неблокирующий) код. Таким образом, это упрощает асинхронное программирование и естественным образом масштабируется.

Во-первых, мы добавим последнюю зависимость ea-async Maven в pom.xml :


    com.ea.async
    ea-async
    1.2.3

Затем давайте преобразуем ранее обсуждавшийся CompletableFuture код с помощью метода await , предоставленного классом Async EA:

static { 
    Async.init(); 
}

public long factorialUsingEAAsync(int number) {
    CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> factorial(number));
    long result = Async.await(completableFuture);
}

Здесь мы вызываем метод Async.init в блоке static для инициализации инструментария Async во время выполнения.

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

Поэтому вызов метода await аналогичен вызову Future.join.

Мы можем использовать параметр – javaagent JVM для инструментирования во время компиляции. Это альтернатива методу Async.init :

java -javaagent:ea-async-1.2.3.jar -cp  

Давайте рассмотрим еще один пример последовательного написания асинхронного кода.

Во-первых, мы выполним несколько операций цепочки асинхронно, используя методы композиции, такие как thenComposeAsync и thenAcceptAsync класса CompletableFuture :

CompletableFuture completableFuture = hello()
  .thenComposeAsync(hello -> mergeWorld(hello))
  .thenAcceptAsync(helloWorld -> print(helloWorld))
  .exceptionally(throwable -> {
      System.out.println(throwable.getCause()); 
      return null;
  });
completableFuture.get();

Затем мы можем преобразовать код с помощью Async.await() советника:

try {
    String hello = await(hello());
    String helloWorld = await(mergeWorld(hello));
    await(CompletableFuture.runAsync(() -> print(helloWorld)));
} catch (Exception e) {
    e.printStackTrace();
}

Реализация напоминает последовательный блокирующий код. Тем не менее, ожидать метод не блокирует код.

Как обсуждалось, все вызовы метода await будут перезаписаны Асинхронным инструментарием для работы аналогично Будущему.присоединиться метод.

Таким образом, как только асинхронное выполнение метода hello завершено, результат Future передается методу mergeWorld . Затем результат передается последнему выполнению с помощью метода CompletableFuture.RunAsync .

5. Кактусы

Cactus-это библиотека Java, основанная на объектно-ориентированных принципах.

Это альтернатива Google Guava и Apache Commons, которая предоставляет общие объекты для выполнения различных операций.

Во-первых, давайте добавим последнюю кактус зависимость Maven:


    org.cactoos
    cactoos
    0.43

Библиотека предоставляет класс Async для асинхронных операций.

Итак, мы можем найти факториал числа, используя экземпляр класса Cactoos Async :

Async asyncFunction = new Async(input -> factorial(input));
Future asyncFuture = asyncFunction.apply(number);
long result = asyncFuture.get();

Здесь метод apply выполняет операцию с использованием метода ExecutorService.submit и возвращает экземпляр Будущего интерфейса .

Аналогично, класс Async имеет метод exec , который предоставляет ту же функцию без возвращаемого значения.

Примечание: библиотека Cactus находится на начальной стадии разработки и может пока не подходить для производственного использования.

6. Jcabi-Аспекты

Jcabi-Aspects предоставляет аннотацию @Async для асинхронного программирования через аспекты AspectJ AOP.

Во-первых, давайте добавим последнюю версию jcabi-аспекты зависимость Maven:


    com.jcabi
    jcabi-aspects
    0.22.6

Библиотека jcabi-aspects требует поддержки среды выполнения AspectJ. Итак, мы добавим зависимость aspectjrt Maven:


    org.aspectj
    aspectjrt
    1.9.5

Затем мы добавим jcabi-maven-плагин плагин, который объединяет двоичные файлы с аспектами AspectJ. Плагин обеспечивает цель ajc , которая выполняет всю работу за нас:


    com.jcabi
    jcabi-maven-plugin
    0.14.1
    
        
            
                ajc
            
        
    
    
        
            org.aspectj
            aspectjtools
            1.9.1
        
        
            org.aspectj
            aspectjweaver
            1.9.1
        
    

Итак, мы все готовы использовать аспекты AOP для асинхронного программирования:

@Async
@Loggable
public Future factorialUsingAspect(int number) {
    Future factorialFuture = CompletableFuture.completedFuture(factorial(number));
    return factorialFuture;
}

Когда мы скомпилируем код, библиотека введет рекомендации AOP вместо аннотации @Async через AspectJ для асинхронного выполнения метода factorialUsingAspect .

Итак, давайте скомпилируем класс с помощью команды Maven:

mvn install

Вывод из jcabi-maven-плагина может выглядеть следующим образом:

 --- jcabi-maven-plugin:0.14.1:ajc (default) @ java-async ---
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-loggable for watching of @Loggable annotated methods
[INFO] Unwoven classes will be copied to /tutorials/java-async/target/unwoven
[INFO] jcabi-aspects 0.18/55a5c13 started new daemon thread jcabi-cacheable for automated cleaning of expired @Cacheable values
[INFO] ajc result: 10 file(s) processed, 0 pointcut(s) woven, 0 error(s), 0 warning(s)

Мы можем проверить, правильно ли сплетен наш класс, проверив журналы в файле jcabi-ajc.log , сгенерированном плагином Maven:

Join point 'method-execution(java.util.concurrent.Future 
com.baeldung.async.JavaAsync.factorialUsingJcabiAspect(int))' 
in Type 'com.baeldung.async.JavaAsync' (JavaAsync.java:158) 
advised by around advice from 'com.jcabi.aspects.aj.MethodAsyncRunner' 
(jcabi-aspects-0.22.6.jar!MethodAsyncRunner.class(from MethodAsyncRunner.java))

Затем мы запустим класс как простое Java-приложение, и результат будет выглядеть следующим образом:

17:46:58.245 [main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-loggable for watching of @Loggable annotated methods
17:46:58.355 [main] INFO com.jcabi.aspects.aj.NamedThreads - 
jcabi-aspects 0.22.6/3f0a1f7 started new daemon thread jcabi-async for Asynchronous method execution
17:46:58.358 [jcabi-async] INFO com.baeldung.async.JavaAsync - 
#factorialUsingJcabiAspect(20): '[email protected][Completed normally]' in 44.64µs

Итак, мы видим, что новый поток демона jcabi-async создается библиотекой, которая выполняла задачу асинхронно.

Аналогично, ведение журнала включено аннотацией @Loggable , предоставленной библиотекой.

7. Заключение

В этой статье мы рассмотрели несколько способов асинхронного программирования на Java.

Для начала мы изучили встроенные функции Java, такие как FutureTask и CompletableFuture для асинхронного программирования. Затем мы увидели несколько библиотек, таких как EA Async и Cactoo, с готовыми решениями.

Кроме того, мы рассмотрели поддержку асинхронного выполнения задач с использованием классов ListenableFuture и Futures Гуавы. Наконец, мы изучили библиотеку jcabi-Аспектов, которая предоставляет функции AOP с помощью аннотации @Async для асинхронных вызовов методов.

Как обычно, все реализации кода доступны на GitHub .