![translation](https://cdn.durumis.com/common/trans.png)
Bu, AI tarafından çevrilen bir gönderidir.
Dil Seç
Text summarized by durumis AI
- Java eşzamansız işlemlerini Spring @Async kullanarak uygulama yöntemini, avantajlarını ve dikkat edilmesi gereken noktaları açıklar.
- Spring @Async'yi kullanarak @EnableAsync anotasyonunu ekleyerek ve eşzamansız işlem yapılmasını istediğiniz metotlara @Async anotasyonunu ekleyerek eşzamansız işlemleri kolayca uygulayabilirsiniz.
- İş parçacığı havuzu ayarlarıyla etkili iş parçacığı yönetimi sağlanabilir ve dönüş tipiyle Future, ListenableFuture ve CompletableFuture gibi yapıları kullanarak eşzamansız işlem sonuçları döndürülebilir.
Java Asenkron İşleme
Spring @Async'i incelemeden önce, senkron, asenkron ve çoklu iş parçacığı kavramları çok önemlidir. Bu kavramları bildiğinizi varsayarak, yalnızca Java asenkron işleme yöntemini kod olarak inceleyelim. Eğer Java iş parçacıklarıyla zaten aşinasanız, bu bölümü geçebilirsiniz.
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 + "");
}
}
Eğer bir mesaj alıp sadece yazdırma işlevi senkron bir şekilde yapılırsa, yukarıdaki gibi kod yazabilirsiniz. Bunu çoklu iş parçacığı asenkron yöntemine dönüştürürsek, kaynak kodunu aşağıdaki gibi yazabiliriz.
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 + "");
}
}
Ancak bu yöntem, iş parçacıklarını yönetemediğimiz için çok tehlikelidir. Örneğin, aynı anda 10.000 çağrı yapılırsa, çok kısa bir sürede 10.000 iş parçacığı oluşturulmalıdır. İş parçacığı oluşturmanın maliyeti az değildir, bu nedenle programın performansını olumsuz etkiler ve hatta OOM hatasına bile yol açabilir. Bu nedenle, iş parçacıklarını yönetmek için bir iş parçacığı havuzu uygulamanız gerekir ve Java, ExecutorService sınıfını sunmaktadır.
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 + "");
}
}
Toplam iş parçacığı sayısını 10'a sınırladık ve istediğimiz çoklu iş parçacığı yöntemindeki asenkron işlemeyi doğru bir şekilde yapabildik. Ancak, yukarıdaki yöntem, asenkron olarak işlenmesini istediğimiz her yöntem için ExecutorService'in submit() yöntemini uygulamayı gerektirir, bu da tekrarlayan değişiklik işleri gerektirir. Yani, başlangıçta senkron bir mantık olarak yazılmış bir yöntemi asenkron olarak değiştirmek istiyorsak, yöntemin kendisinin mantığında değişiklik yapmak kaçınılmazdır.
Spring @Async
Basit Yöntem
@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 + "");
}
}
@EnableAsync ek açıklamasını Application sınıfının üzerine ekleyin ve asenkron olarak işlenmesini istediğiniz senkron mantık yönteminin üzerine @Async ek açıklamasını yapın, bu kadar. Ancak yukarıdaki yöntem, iş parçacıklarını yönetmediği sorununa sahiptir. Çünkü @Async'in varsayılan ayarı, SimpleAsyncTaskExecutor'ı kullanacak şekilde ayarlanmıştır ve bu, bir iş parçacığı havuzu değil, yalnızca iş parçacığı oluşturma işlevi görür.
Bir İş Parçacığı Havuzu Kullanma Yöntemi
Öncelikle Application sınıfındaki @EnableAsync'i kaldırın. Application sınıfında @EnableAutoConfiguration veya @SpringBootApplication yapılandırması varsa, çalışma zamanında @Configuration olarak yapılandırılmış SpringAsyncConfig sınıfının (aşağıda oluşturulacak) threadPoolTaskExecutor fasulye bilgilerini okuyacağı için.
@Configuration
@EnableAsync // Application'da değil, Async yapılandırma sınıfında eklenmelidir.
public class SpringAsyncConfig {
@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(3); // Temel iş parçacığı sayısı
taskExecutor.setMaxPoolSize(30); // Maksimum iş parçacığı sayısı
taskExecutor.setQueueCapacity(100); // Kuyruk boyutu
taskExecutor.setThreadNamePrefix("Executor-");
return taskExecutor;
}
Çekirdek ve maksimum boyutları ayarlayabilirsiniz. Bu durumda, başlangıçta çekirdek boyutu kadar çalışacak ve işlem hızı düşerse, maksimum boyut kadar iş parçacığı artacağını tahmin edebilirsiniz, ancak bu doğru değil.
İçsel olarak, Integer.MAX_VALUE boyutunda bir LinkedBlockingQueue oluşturur ve çekirdek boyutu kadar iş parçacığı işlem yapamazsa, kuyrukta bekler. Kuyruk dolarsa, maksimum boyut kadar iş parçacığı oluşturarak işlem yapar.
Bu durumda, kuyruk boyutunu Integer.MAX_VALUE olarak ayarlamak zorsa, queueCapacity'yi ayarlayabilirsiniz. Yukarıdaki gibi yapılandırılırsa, başlangıçta 3 iş parçacığı tarafından işlem yapılır ve işlem hızı düşerse, işleri 100 boyutlu bir kuyruğa yerleştirir ve daha fazla istek gelirse, en fazla 30 iş parçacığı oluşturarak işleri işler.
İş parçacığı havuzu yapılandırması tamamlandıktan sonra, @Async ek açıklaması olan yöntemde yukarıdaki fasulye adını ekleyebilirsiniz.
@Service
public class MessageService {
@Async("threadPoolTaskExecutor")
public void print(String message) {
System.out.println(message);
}
Birkaç farklı iş parçacığı havuzu türünü yapılandırmak istiyorsanız, threadPoolTaskExecutor() gibi fasulye oluşturma yöntemlerini oluşturun ve @Async'i yapılandırırken istediğiniz iş parçacığı havuzu fasulyesini ekleyin.
Dönüş Tipine Göre Dönüş Biçimi
Dönüş değeri yoksa
Asenkron olarak işlenmesi gereken yöntemin işlem sonucunu iletmesi gerekmiyorsa, @Async ek açıklamasının dönüş tipini void olarak ayarlayabilirsiniz.
Dönüş değeri varsa
Future, ListenableFuture, CompletableFuture türlerini dönüş türü olarak kullanabilirsiniz. Asenkron yöntemin dönüş biçimini new AsyncResult() ile sarmalayabilirsiniz.
[Future]
@Service
public class MessageService {
@Async
public Future print(String message) throws InterruptedException {
System.out.println("Task Start - " + 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());
}
}
}
// Çalışma Sonuçları
Task Start - 1
jayon-1
Task Start - 2
jayon-2
Task Start - 3
jayon-3
Task Start - 4
jayon-4
Task Start - 5
future.get(), isteğin sonucu gelene kadar bekleme işlevi görür. Bu nedenle, asenkron engelleme yöntemi haline gelir ve performans iyi değildir. Genellikle Future kullanılmaz.
[ListenableFuture]
@Service
public class MessageService {
@Async
public ListenableFuture print(String message) throws InterruptedException {
System.out.println("Task Start - " + 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()));
}
}
}
// Çalışma Sonuçları
Task Start - 1
Task Start - 3
Task Start - 2
jayon-1
jayon-2
Task Start - 5
jayon-3
Task Start - 4
jayon-4
ListenableFuture, geri çağrıları kullanarak engellemeyen bir şekilde işlem yapabilir. addCallback() yönteminin ilk parametresi işlem tamamlama geri çağrı yöntemidir, ikinci parametre ise işlem başarısızlık geri çağrı yöntemidir. İş parçacığı havuzunun çekirdek iş parçacıklarını 3 olarak ayarladığımız için, "Task Start" mesajının ilk olarak 3 adet yazıldığını görebilirsiniz.
[CompletableFuture]
ListenableFuture ile de engellemeyen mantık uygulayabiliriz, ancak geri çağrı içinde geri çağrıya ihtiyaç duyulursa, "geri çağrı cehennemi" olarak adlandırılan çok karmaşık bir kodu tetikler.
Elbette bu sefer CompletableFuture'ı ayrıntılı olarak ele almayacağız, bu nedenle karmaşık geri çağrıları ele alan bir kod merak ediyorsanız, aşağıdaki kaynak bağlantısını inceleyebilirsiniz.
@Service
public class MessageService {
@Async
public CompletableFuture print(String message) throws InterruptedException {
System.out.println("Task Start - " + 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;
});
}
}
ListenableFuture'daki geri çağrı tanımından daha okunabilir ve engellemeyen işlevi mükemmel bir şekilde gerçekleştirir. Bu nedenle, @Async kullanırken, dönüş değerine ihtiyaç duyulursa, CompletableFuture kullanılması önerilir.
@Async'in Avantajları
Geliştiriciler, yöntemleri senkron olarak yazabilir ve asenkron bir yöntem isterlerse, sadece @Async ek açıklamasını yöntemin üzerine ekleyebilirler. Bu nedenle, senkron ve asenkron açısından iyi bir bakım kodu oluşturabilirsiniz.
@Async'in Dikkat Edilmesi Gereken Noktalar
@Async işlevini kullanmak için @EnableAsync ek açıklamasını bildirmeniz gerekir, ancak bu durumda ek bir yapılandırma yapılmazsa, vekil modunda çalışır. Yani, @Async ek açıklamasıyla çalışan asenkron yöntemler, tüm Spring AOP kısıtlamalarını olduğu gibi takip eder. Daha fazla bilgi içinBu yayın'a bakın.
- Özel yöntemlerde @Async kullanılırsa bile, AOP çalışmaz.
- Aynı nesne içindeki yöntemler birbirini çağırırsa, AOP çalışmaz.