Ini adalah postingan yang diterjemahkan oleh AI.
Pilih Bahasa
Teks yang dirangkum oleh AI durumis
- Artikel ini menjelaskan cara mengimplementasikan pemrosesan asinkron Java menggunakan Spring @Async, keuntungannya, dan hal-hal yang perlu diperhatikan.
- Dengan menggunakan Spring @Async, Anda dapat dengan mudah mengimplementasikan pemrosesan asinkron dengan menambahkan anotasi @EnableAsync dan menempelkan anotasi @Async pada metode yang ingin Anda proses secara asinkron.
- Konfigurasi thread pool memungkinkan manajemen thread yang efisien, dan berdasarkan tipe kembaliannya, Anda dapat menggunakan Future, ListenableFuture, atau CompletableFuture untuk mengembalikan hasil pemrosesan asinkron.
Pemrosesan Asinkron Java
Sebelum melihat Spring @Async, penting untuk memahami konsep sinkron, asinkron, dan multithreading. Dengan asumsi Anda memahami konsep ini, mari kita lihat cara pemrosesan asinkron Java murni melalui kode. Jika Anda sudah familier dengan Java threading, Anda dapat melewati bab ini.
public class MessageService {
public void print(String message) {
System.out.println(message);
}
}
public class Main {
public static void main(String[] args) {
MessageService messageService = new MessageService();
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
Jika Anda membuat fungsi untuk menerima message dan hanya menampilkannya secara sinkron, Anda dapat menulis kode seperti di atas. Jika Anda mengubahnya menjadi cara asinkron multi-threading, Anda dapat menulis kode sumber seperti ini.
public class MessageService {
public void print(String message) {
new Thread(() -> System.out.println(message))
.start();
}
}
public class Main {
public static void main(String[] args) {
MessageService messageService = new MessageService();
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
Namun, metode ini sangat berbahaya karena Anda tidak dapat mengelola Thread. Misalnya, jika ada 10.000 panggilan secara bersamaan, Anda harus membuat 10.000 Thread dalam waktu yang sangat singkat. Biaya membuat Thread tidak sedikit, sehingga dapat berdampak buruk pada kinerja program, dan bahkan dapat menyebabkan kesalahan OOM. Oleh karena itu, Anda harus mengimplementasikan Thread Pool untuk mengelola Thread, dan Java menyediakan kelas ExecutorService.
public class MessageService {
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public void print(String message) {
executorService.submit(() -> System.out.println(message));
}
}
public class Main {
public static void main(String[] args) {
MessageService messageService = new MessageService();
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
Kami membatasi jumlah total Thread menjadi 10, dan kami dapat melakukan pemrosesan asinkron multi-threading yang kami inginkan dengan benar. Namun, metode di atas mengharuskan Anda untuk menerapkan metode submit() dari ExecutorService ke setiap metode yang ingin Anda proses secara asinkron, sehingga Anda harus melakukan pekerjaan perbaikan berulang. Artinya, jika Anda ingin mengubah metode yang awalnya ditulis sebagai logika sinkron menjadi asinkron, perubahan pada logika metode itu sendiri tidak dapat dihindari.
Spring @Async
Cara Sederhana
@EnableAsync
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Service
public class MessageService {
@Async
public void print(String message) {
System.out.println(message);
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() {
for (int i = 1; i <= 100; i++) {
messageService.print(i + "");
}
}
Tempelkan anotasi @EnableAsync di atas kelas Aplikasi Anda, dan tempelkan anotasi @Async di atas metode logika sinkron yang ingin Anda proses secara asinkron. Itu saja. Namun, metode di atas memiliki masalah dalam tidak mengelola thread. Ini karena pengaturan default @Async adalah untuk menggunakan SimpleAsyncTaskExecutor, yang bukan kumpulan thread dan hanya berfungsi untuk membuat thread.
Cara Menggunakan Thread Pool
Pertama, hapus @EnableAsync dari kelas Aplikasi Anda. Jika kelas Aplikasi Anda memiliki pengaturan @EnableAutoConfiguration atau @SpringBootApplication, maka pada saat runtime, ia akan membaca informasi tentang bean threadPoolTaskExecutor (yang akan kita buat di bawah) dari kelas SpringAsyncConfig yang dikonfigurasi @Configuration.
@Configuration
@EnableAsync // Harus dilampirkan pada kelas konfigurasi Async, bukan pada Aplikasi
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3); // Jumlah thread default
taskExecutor.setMaxPoolSize(30); // Jumlah thread maksimum
taskExecutor.setQueueCapacity(100); // Ukuran Queue
taskExecutor.setThreadNamePrefix("Executor-");
return taskExecutor;
}
Anda dapat mengatur ukuran core dan max. Pada saat ini, Anda mungkin berharap bahwa ia akan beroperasi dengan ukuran core awal dan kemudian meningkatkan jumlah Thread hingga ukuran max jika tidak dapat memproses tugas lagi. Namun, itu tidak benar.
Secara internal, ia membuat LinkedBlockingQueue dengan ukuran Integer.MAX_VALUE dan menunggu di Queue jika Thread dengan ukuran core tidak dapat memproses tugas. Jika Queue penuh, maka ia akan membuat Thread dengan ukuran max untuk memproses tugas.
Jika Anda merasa tidak nyaman menetapkan ukuran Queue menjadi Integer.MAX_VALUE, Anda dapat mengatur queueCapacity. Dengan pengaturan seperti di atas, ia akan diproses oleh 3 Thread awal, dan jika kecepatan pemrosesan melambat, tugas akan ditempatkan di Queue berukuran 100. Jika ada permintaan lebih lanjut, ia akan membuat hingga 30 Thread untuk memproses tugas.
Setelah konfigurasi kumpulan thread selesai, Anda dapat melampirkan nama bean di atas ke metode yang memiliki anotasi @Async.
@Service
public class MessageService {
@Async("threadPoolTaskExecutor")
public void print(String message) {
System.out.println(message);
}
Jika Anda ingin menetapkan beberapa jenis kumpulan thread, buat beberapa metode pembuatan bean seperti threadPoolTaskExecutor(), dan masukkan bean kumpulan thread yang Anda inginkan saat mengatur @Async.
Bentuk Pengembalian Berdasarkan Jenis Pengembalian
Jika tidak ada nilai pengembalian
Ini adalah kasus ketika metode yang harus diproses secara asinkron tidak perlu mengirimkan hasil pemrosesan. Dalam kasus ini, atur tipe pengembalian anotasi @Async menjadi void.
Jika ada nilai pengembalian
Anda dapat menggunakan tipe Future, ListenableFuture, CompletableFuture sebagai tipe pengembalian. Bungkus bentuk pengembalian metode asinkron dengan new AsyncResult().
[Masa Depan]
@Service
public class MessageService {
@Async
public Future print(String message) throws InterruptedException {
System.out.println("Mulai Tugas - " + message);
Thread.sleep(3000);
return new AsyncResult<>("jayon-" + message);
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() throws ExecutionException, InterruptedException {
for (int i = 1; i <= 5; i++) {
Future future = messageService.print(i + "");
System.out.println(future.get());
}
}
}
// Hasil eksekusi
Mulai Tugas - 1
jayon-1
Mulai Tugas - 2
jayon-2
Mulai Tugas - 3
jayon-3
Mulai Tugas - 4
jayon-4
Mulai Tugas - 5
future.get() berfungsi untuk menunggu hingga hasil permintaan diterima melalui pemblokiran. Karena itu, ia menjadi metode pemblokiran asinkron dan kinerjanya tidak baik. Biasanya, Future tidak digunakan.
[ListenableFuture]
@Service
public class MessageService {
@Async
public ListenableFuture print(String message) throws InterruptedException {
System.out.println("Mulai Tugas - " + message);
Thread.sleep(3000);
return new AsyncResult<>("jayon-" + message);
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() throws InterruptedException {
for (int i = 1; i <= 5; i++) {
ListenableFuture listenableFuture = messageService.print(i + "");
listenableFuture.addCallback(System.out::println, error -> System.out.println(error.getMessage()));
}
}
}
// Hasil eksekusi
Mulai Tugas - 1
Mulai Tugas - 3
Mulai Tugas - 2
jayon-1
jayon-2
Mulai Tugas - 5
jayon-3
Mulai Tugas - 4
jayon-4
ListenableFuture memungkinkan Anda untuk memproses tugas secara non-blocking melalui callback. Parameter pertama dari metode addCallback() adalah metode callback penyelesaian tugas, dan parameter kedua adalah metode callback kegagalan tugas. Sebagai catatan, karena ukuran thread core kumpulan thread diatur ke 3, Anda dapat melihat bahwa pesan "Mulai Tugas" pertama kali dicetak 3 kali.
[CompletableFuture]
Meskipun Anda dapat mengimplementasikan logika non-blocking hanya dengan ListenableFuture, jika Anda membutuhkan callback dalam callback, itu dapat menyebabkan kode yang sangat rumit yang disebut callback hell.
Tentu saja, kita tidak akan membahas CompletableFuture secara mendetail kali ini, jadi jika Anda ingin mengetahui kode yang menangani callback yang rumit, silakan lihat tautan sumber di bawah.
@Service
public class MessageService {
@Async
public CompletableFuture print(String message) throws InterruptedException {
System.out.println("Mulai Tugas - " + message);
Thread.sleep(3000);
return new AsyncResult<>("jayon-" + message).completable();
}
}
@RequiredArgsConstructor
@RestController
public class MessageController {
private final MessageService messageService;
@GetMapping("/messages")
@ResponseStatus(code = HttpStatus.OK)
public void printMessage() throws InterruptedException {
for (int i = 1; i <= 5; i++) {
CompletableFuture completableFuture = messageService.print(i + "");
completableFuture
.thenAccept(System.out::println)
.exceptionally(error -> {
System.out.println(error.getMessage());
return null;
});
}
}
Kejelasan kode lebih baik daripada definisi callback dari ListenableFuture, dan ia melakukan fungsi non-blocking dengan sempurna. Oleh karena itu, disarankan untuk menggunakan CompletableFuture ketika Anda membutuhkan nilai pengembalian saat menggunakan @Async.
Keuntungan @Async
Pengembang dapat menulis metode secara sinkron, dan jika mereka menginginkan metode asinkron, mereka dapat menempelkan anotasi @Async di atas metode tersebut. Oleh karena itu, Anda dapat membuat kode yang mudah dipelihara untuk sinkron dan asinkron.
Hal yang Perlu Diperhatikan @Async
Untuk menggunakan fungsi @Async, Anda harus mendeklarasikan anotasi @EnableAsync. Pada saat itu, jika Anda tidak melakukan pengaturan khusus, ia akan beroperasi dalam mode proxy. Artinya, metode asinkron yang beroperasi dengan anotasi @Async akan mengikuti batasan Spring AOP sebagaimana adanya. Untuk alasan yang lebih spesifik, silakan lihat postingan ini.
- AOP tidak akan berfungsi meskipun Anda melampirkan @Async ke metode privat.
- AOP tidak akan berfungsi jika metode dalam objek yang sama saling memanggil.