Pengaturcaraan Asinkron di Jawa

1. Gambaran keseluruhan

Dengan permintaan yang semakin meningkat untuk menulis kod yang tidak menyekat, kami memerlukan cara untuk melaksanakan kod tersebut secara tidak segerak.

Dalam tutorial ini, kita akan melihat beberapa cara untuk mencapai pengaturcaraan asinkron di Java. Juga, kita akan meneroka beberapa perpustakaan Java yang memberikan penyelesaian luar kotak.

2. Pengaturcaraan Asinkron di Jawa

2.1. Benang

Kami boleh membuat utas baru untuk melakukan sebarang operasi secara tidak segerak. Dengan pembebasan ungkapan lambda di Java 8, ia lebih bersih dan lebih mudah dibaca.

Mari buat utas baru yang menghitung dan mencetak faktor nombor:

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

2.2. Tugas Masa Depan

Sejak Java 5, antara muka Future menyediakan cara untuk melakukan operasi asinkron menggunakan FutureTask .

Kita boleh menggunakan mengemukakan kaedah yang ExecutorService untuk melaksanakan tugas yang tak segerak dan kembali atas kehendak FutureTask .

Oleh itu, mari kita cari faktor nombor:

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();

Di sini, kami telah menggunakan kaedah isDone yang disediakan oleh antara muka Masa Depan untuk memeriksa sama ada tugas itu selesai. Setelah selesai, kita boleh mendapatkan hasilnya menggunakan kaedah get .

2.3. Selesai Masa Depan

Java 8 memperkenalkan CompletableFuture dengan gabungan Future dan CompletionStage . Ia menyediakan pelbagai kaedah seperti supplyAsync , runAsync , dan kemudianApplyAsync untuk pengaturcaraan tak segerak.

Oleh itu, mari kita gunakan CompletableFuture sebagai ganti FutureTask untuk mencari faktor nombor:

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

Kami tidak perlu menggunakan ExecutorService secara eksplisit. The CompletableFuture secara dalaman menggunakan ForkJoinPool untuk menangani tugas secara tidak segerak . Oleh itu, ini menjadikan kod kita lebih bersih.

3. Jambu batu

Jambu biji menyediakan kelas ListenableFuture untuk melakukan operasi tak segerak.

Pertama, kami akan menambahkan pergantungan jambu batu Maven terkini :

 com.google.guava guava 28.2-jre 

Kemudian, mari cari faktorial nombor menggunakan ListenableFuture :

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

Di sini, kelas MoreExecutors memberikan contoh kelas ListeningExecutorService . Kemudian, kaedah ListeningExecutorService.submit melaksanakan tugas secara tidak segerak dan mengembalikan contoh ListenableFuture .

Jambu batu juga mempunyai Hadapan kelas yang menyediakan kaedah seperti submitAsync , scheduleAsync dan transformAsync untuk rantai ListenableFutures sama dengan CompletableFuture.

Sebagai contoh, mari kita lihat cara menggunakan Futures.submitAsync sebagai ganti kaedah 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);

Di sini, submitAsync kaedah memerlukan hujah AsyncCallable , yang dicipta menggunakan Callables kelas.

Selain itu, kelas Futures menyediakan kaedah addCallback untuk mendaftarkan panggilan balik kejayaan dan kegagalan:

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

4. EA Async

Electronic Arts membawa ciri async-tunggu dari .NET ke ekosistem Java melalui perpustakaan ea-async .

Perpustakaan membenarkan penulisan kod tak segerak (tidak menyekat) secara berurutan. Oleh itu, ia menjadikan pengaturcaraan asinkron lebih mudah dan disesuaikan secara semula jadi.

Pertama, kami akan menambahkan pergantungan ea-async Maven terbaru ke pom.xml :

 com.ea.async ea-async 1.2.3 

Kemudian, mari kita ubah kod CompletableFuture yang telah dibincangkan sebelum ini dengan menggunakan kaedah tunggu yang disediakan oleh kelas Async EA :

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

Di sini, kami membuat panggilan ke kaedah Async.init di blok statik untuk memulakan instrumen jangka masa Async .

Instrumentasi Async mengubah kod pada waktu runtime dan menulis semula panggilan ke kaedah tunggu , untuk berkelakuan sama dengan menggunakan rantai CompletableFuture .

Oleh itu, kaedah panggilan ke menunggu adalah serupa dengan memanggil Future.join.

Kita dapat menggunakan parameter - javaagent JVM untuk instrumentasi waktu kompilasi. Ini adalah alternatif kepada kaedah Async.init :

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

Mari kita teliti satu lagi contoh penulisan kod tak segerak secara berurutan.

Pertama, kami akan melakukan beberapa operasi rantai secara serentak menggunakan kaedah komposisi seperti kemudianComposeAsync dan kemudianAcceptAsync dari kelas CompletableFuture :

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

Then, we can transform the code using EA's Async.await():

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

The implementation resembles the sequential blocking code. However, the await method doesn't block the code.

As discussed, all calls to the await method will be rewritten by the Async instrumentation to work similarly to the Future.join method.

So, once the asynchronous execution of the hello method is finished, the Future result is passed to the mergeWorld method. Then, the result is passed to the last execution using the CompletableFuture.runAsync method.

5. Cactoos

Cactoos is a Java library based on object-oriented principles.

It is an alternative to Google Guava and Apache Commons that provides common objects for performing various operations.

First, let's add the latest cactoos Maven dependency:

 org.cactoos cactoos 0.43 

The library provides an Async class for asynchronous operations.

So, we can find the factorial of a number using the instance of Cactoos's Async class:

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

Here, the apply method executes the operation using the ExecutorService.submit method and returns an instance of the Future interface.

Similarly, the Async class has the exec method that provides the same feature without a return value.

Note: the Cactoos library is in the initial stages of development and may not be appropriate for production use yet.

6. Jcabi-Aspects

Jcabi-Aspects provides the @Async annotation for asynchronous programming through AspectJ AOP aspects.

First, let's add the latest jcabi-aspects Maven dependency:

 com.jcabi jcabi-aspects 0.22.6  

The jcabi-aspects library requires AspectJ runtime support. So, we'll add the aspectjrt Maven dependency:

 org.aspectj aspectjrt 1.9.5  

Next, we'll add the jcabi-maven-plugin plugin that weaves the binaries with AspectJ aspects. The plugin provides the ajc goal that does all the work for us:

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

So, we're all set to use the AOP aspects for asynchronous programming:

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

When we compile the code, the library will inject AOP advice in place of the @Async annotation through AspectJ weaving, for the asynchronous execution of the factorialUsingAspect method.

So, let's compile the class using the Maven command:

mvn install

The output from the jcabi-maven-plugin may look like:

 --- 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)

We can verify if our class is woven correctly by checking the logs in the jcabi-ajc.log file, generated by the Maven plugin:

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))

Then, we'll run the class as a simple Java application, and the output will look like:

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

So, we can see a new daemon thread jcabi-async is created by the library that performed the task asynchronously.

Similarly, the logging is enabled by the @Loggable annotation provided by the library.

7. Conclusion

In this article, we've seen a few ways of asynchronous programming in Java.

To begin with, we explored Java's in-built features like FutureTask and CompletableFuture for asynchronous programming. Then, we've seen a few libraries like EA Async and Cactoos with out-of-the-box solutions.

Also, we examined the support of performing tasks asynchronously using Guava's ListenableFuture and Futures classes. Last, we explored the jcabi-AspectJ library that provides AOP features through its @Async annotation for asynchronous method calls.

Seperti biasa, semua pelaksanaan kod tersedia di GitHub.