1. Thread.interrupt() və kooperativ ləğv
Real tətbiqlərdə tapşırıqlar uzun ola bilər və bəzən ümumiyyətlə “asılı qala” bilər — şəbəkə, fayllar və ya xarici servislərlə işləyərkən. İstifadəçi əməliyyatı ləğv edə bilər, server — sorğunun emalını dayandıra bilər, yaxud sadəcə ümumi taym‑aut bitə bilər. Tapşırıqları düzgün ləğv etməyi bacarmasanız, tətbiq ilişib qalacaq, resursları boş yerə sərf edəcək və xarici hadisələrə pis reaksiya verəcək.
Açar fikir: ləğv kooperativ olmalıdır — tapşırıq özü yoxlamalıdır ki, ondan tamamlanması xahiş olunub‑olunmadığını və resursları düzgün azad etməlidir.
Thread.interrupt() necə işləyir
Hər bir axının “interrupt” bayrağı var. thread.interrupt() çağırdıqda, bu bayraq true olur. Axının özü isə “öldürülmür”; o, statusunu yoxlayıb tamamlanmalıdır: vaxtaşırı Thread.currentThread().isInterrupted() çağırmaq və səlis çıxmaq.
Nümunə:
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// İşləyirik...
try {
Thread.sleep(100); // Kəsilə bilər
} catch (InterruptedException e) {
// Bayraq sıfırlanır, lakin özümüzü yenidən kəsə bilərik
Thread.currentThread().interrupt();
break;
}
}
System.out.println("Axın kəsilmə səbəbindən tamamlandı.");
});
worker.start();
// ... sonra
worker.interrupt();
Bayraq harada avtomatik işləyir?
- Bloklana bilən metodlar (sleep, wait, join, bloklayan strukturların əməliyyatları) kəsilmə zamanı InterruptedException atır.
- Qalan hallarda (məsələn, hesablayıcı dövrədə) isInterrupted() əl ilə yoxlanmalıdır.
“Bayrağı qoy — və tez çıx” şablonu
- Çağıran kodda: thread.interrupt()
- Tapşırıqda: vaxtaşırı Thread.currentThread().isInterrupted() yoxlamaq
- Lazım gələrsə — resursları düzgün azad etmək və tamamlanmaq.
Tipik səhv: interrupt() çağırışının axını dərhal “öldürəcəyini” gözləmək. Xeyr — bu yalnız siqnaldır, tapşırıq özü reaksiya verməlidir.
2. Future.cancel(), CancellationException və tapşırıqların ləğvi
Future.cancel necə işləyir
ExecutorService.submit() vasitəsilə tapşırığı işə saldıqda, Future obyekti alırsınız. Onun cancel(boolean mayInterruptIfRunning) metodu var:
- Əgər tapşırıq hələ başlanmayıbsa — o, işə salınmayacaq.
- Əgər tapşırıq artıq icra olunur və mayInterruptIfRunning == true isə — tapşırığı icra edən axına interrupt() çağırılacaq.
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<?> future = executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
// Uzunmüddətli iş
}
System.out.println("Tapşırıq ləğv səbəbindən tamamlandı.");
});
// ... sonra
future.cancel(true); // Tapşırığın ləğvini xahiş edək
Tapşırıqla əslində nə baş verir
Future vasitəsilə ləğv — “axını öldür” kimi sehrli düymə deyil, əslində Thread.interrupt()-un nəzakətli formasıdır. Tapşırıq kəsilmə bayrağını düzgün yoxlayırsa — o, səliqəli tamamlanacaq. Yoxsa — təbii tamamlanana qədər işləməyə davam edəcək.
Ləğvdən sonra future.get() çağırsanız, CancellationException alacaqsınız — tapşırığın ləğv edildiyini xatırladan bir siqnal.
3. CompletableFuture: ləğv, taym‑autlar və zəncirlər
CompletableFuture ləğvi
CompletableFuture-də də cancel(boolean) var. Əgər tapşırıq hələ başa çatmayıbsa, o ləğv ediləcək və sonrakı bütün emalçılar (thenApply, thenAccept və s.) çağırılmayacaq.
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
while (!Thread.currentThread().isInterrupted()) {
// İşləyirik...
}
System.out.println("CF ləğv səbəbindən tamamlandı.");
});
// ... sonra
cf.cancel(true);
Taym‑autlar: orTimeout və completeOnTimeout
- orTimeout(timeout, unit) — vaxt bitərsə, CompletableFuture-i TimeoutException ilə tamamlayır.
- completeOnTimeout(value, timeout, unit) — vaxt çatmasa, göstərilən dəyərlə tamamlayır.
CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(5000); } catch (InterruptedException e) {}
return "OK";
});
cf.orTimeout(2, TimeUnit.SECONDS)
.exceptionally(ex -> "TIMEOUT")
.thenAccept(System.out::println); // 2 saniyədən sonra: "TIMEOUT"
Zəncirlərdə ləğvin ötürülməsi
Əgər “yuxarıdakı” CompletableFuture ləğv edilsə, zəncirdəki bütün sonrakı addımlar çağırılmayacaq. Lakin daxili asinxron əməliyyatları işə salmaq üçün thenCompose istifadə olunursa, ləğv avtomatik “yuxarı” ötürülmür — bunu açıq şəkildə layihələndirmək lazımdır (statusu yoxlamaq, övlad tapşırıqları ləğv etmək, ümumi deadline istifadə etmək).
Diqqətli olun thenCompose və xüsusi Executor ilə! Daxili tapşırıqların kəsilmə/ləğvə reaksiya verə bildiyinə və/və ya ümumi taym‑aut aldıqlarına əmin olun.
4. StructuredTaskScope: tapşırıqlar qrupunun ləğvi
Structured Concurrency və ləğv
StructuredTaskScope (Java 21+) tapşırıqlar qrupunu işə salmağa və onların həyat dövrünü vahid bütöv kimi idarə etməyə imkan verir. Əgər tapşırıqlardan biri səhvlə tamamlanarsa və ya deadline bitərsə — qalan tapşırıqlar avtomatik ləğv edilir.
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> f1 = scope.fork(() -> fetchData1());
Future<String> f2 = scope.fork(() -> fetchData2());
scope.join(); // bütün tapşırıqların tamamlanmasını gözləyirik
scope.throwIfFailed(); // azı biri uğursuz olduqda — istisna atırıq
String result = f1.resultNow() + f2.resultNow();
System.out.println(result);
}
- Hər hansı tapşırıq səhvlə bitsə — scope bütün digər tapşırıqları ləğv edir.
- Deadline bitərsə (scope.joinUntil(deadline) vasitəsilə) — scope bütün tapşırıqları ləğv edir.
Tamamlama siyasətləri
- ShutdownOnFailure — ilk səhvdə bütün tapşırıqları ləğv edir.
- ShutdownOnSuccess — tapşırıqlardan biri uğurla bitən kimi qalanları ləğv edir.
5. Praktika: təhlükəsiz şəkildə uzun əməliyyatların ləğvi
Nümunə: bloklayan IO‑nun ləğvi
Əgər tapşırıq fayldan və ya şəbəkədən oxuma zamanı bloklanıbsa, axını kəsmək hər zaman kömək etmir — bəzi IO əməliyyatları interrupt-a reaksiya vermir. Müasir API‑lərdə (NIO, AsynchronousFileChannel) kəsilmə dəstəyi daha yaxşıdır, amma hələ də hər yerdə deyil.
Tövsiyələr:
- Ləğv lazımdırsa, bloklanmayan IO istifadə edin.
- Bloklayan IO üçün — API səviyyəsində taym‑autlar təyin edin (məsələn, Socket.setSoTimeout).
- Asinxron tapşırıqlar üçün — Future.cancel istifadə edin və kəsilməyə düzgün reaksiya verin.
Nümunə: növbə/barier gözləməsinin ləğvi
Bir çox sinxronizatorlar (BlockingQueue.take(), CountDownLatch.await(), CyclicBarrier.await()) kəsilmə zamanı InterruptedException atır. Handler‑də istisnanı tutun, lazım gələrsə bayrağı bərpa edin və tapşırığı düzgün tamamlayın.
6. “time‑budget” nümunəsi: əməliyyatlar qrupu üçün ümumi deadline
Mürəkkəb tətbiqlərdə tez‑tez əməliyyatlar qrupunun icrasına ümumi taym‑aut təyin etmək lazım olur. Məsələn, istifadəçi cavabı 2 saniyədən artıq gözləmir, daxilində 3 şəbəkə sorğusu etmək lazımdırsa — onların hamısı ümumi deadline‑a sığmalıdır.
Deadline‑ı stekin aşağısına necə ötürməli?
- Deadline obyektini (məsələn, Instant deadline) potensial bloklayan bütün metodlara ötürün.
- Hər bir metodda qalan vaxtı hesablayın: Duration.between(Instant.now(), deadline).
- Bu vaxtdan bloklayan əməliyyatlarda taym‑autlar üçün istifadə edin (await(timeout), poll(timeout), orTimeout(timeout)).
Instant deadline = Instant.now().plusSeconds(2);
void doWork(Instant deadline) throws TimeoutException, InterruptedException {
Duration left = Duration.between(Instant.now(), deadline);
if (left.isNegative() || left.isZero()) throw new TimeoutException();
// Taym-out üçün left-dən istifadə edirik
queue.poll(left.toMillis(), TimeUnit.MILLISECONDS);
}
Scoped Values / kontekst
Java 21+ üçün Scoped Values vasitəsilə deadline‑ı çağırış steki boyunca ötürmək olar ki, onu hər metoda ayrıca ötürməyəsiniz.
7. Structured Concurrency: nasazlıq/taym‑aut zamanı bütün scope‑un ləğvi
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> f1 = scope.fork(() -> fetchData1());
Future<String> f2 = scope.fork(() -> fetchData2());
boolean completed = scope.joinUntil(Instant.now().plusSeconds(2));
if (!completed) {
scope.shutdown();
throw new TimeoutException("Deadline keçdi!");
}
scope.throwIfFailed();
// ...
}
- Deadline bitərsə — scope bütün tapşırıqları ləğv edir.
- Tapşırıqlardan biri uğursuz olarsa — qalanlar avtomatik ləğv olunur.
8. Ləğv və taym‑autlarla işdə tipik səhvlər
Səhv №1: Gözləmək ki, interrupt() axını dərhal tamamlayacaq. Əslində bu yalnız siqnaldır — tapşırıq statusu özü yoxlamalı və düzgün tamamlanmalıdır.
Səhv №2: Yoxlanılmır isInterrupted() uzun dövrlərdə. Kəsilmə bayrağı yoxlanılmasa, tapşırıq ondan tamamlanması xahiş olunsa belə, sonsuz işləyə bilər.
Səhv №3: Future.cancel() tapşırıq interrupt‑a reaksiya vermirsə, ləğvə gətirmir. Tapşırıq “kar”dırsa, cancel() kömək etməyəcək.
Səhv №4: Taym‑autlar stekin aşağısına ötürülmür. Deadline bütün metodlara ötürülməsə, daxili əməliyyat lazım olandan daha uzun “asılı qala” bilər.
Səhv №5: thenCompose zəncirlərində CompletableFuture ləğvi avtomatik ötürülmür. “Yuxarıdakı” future ləğv edilərsə, daxili tapşırıqlar işləməyə davam edə bilər — ləğvi açıq şəkildə işləyin.
Səhv №6: StructuredTaskScope bağlanmır (try‑with‑resources yoxdur). Scope bağlanmasa, övlad tapşırıqlar “asılı” qala bilər.
GO TO FULL VERSION