Concurrency API
ExecutorService, Future, CompletableFuture és ForkJoinPool
A Java Concurrency API a task-alapú concurrency szabványos eszköztára. Lecseréli az ad-hoc threadgyártást explicit executorra, result handle-ökre, aszinkron kompozícióra és strukturált pool lifecycle-ra. Interjún ezzel azt mérik, hogy tudsz-e overloadról, backpressure-ről, cancellationről és pool ownershipről beszélni, nem csak osztályneveket felsorolni.
1. Definíció
Mi a Concurrency API?
A szabványos Java absztrakciók együttese, amelyekkel taskokat tudsz futtatni anélkül, hogy minden munkaegységhez külön raw threadet kezelnél.
Az alap elemek közé tartozik:
ExecutorExecutorServiceScheduledExecutorServiceFutureCompletableFutureForkJoinPool
Miért fontos ez?
A raw thread egyetlen helyre köti össze a következőket:
- task submission
- execution resource
- lifecycle
- shutdown szemantika
A Concurrency API ezeket szétválasztja.
Ez teszi lehetővé, hogy tudatosan gondolkodj:
- capacityről
- queueingről
- rejectionről
- cancellationről
- result propagationről
- ownershipről
Mit mond egy erős válasz?
Egy erős válasz elmagyarázza:
- miért jobb többnyire executorokat használni raw thread helyett
- miért architekturális döntés a pool sizing és a queueing
- miért korlátozott a
Futurekompozíciós szempontból - miért jobb az orchestrationhöz a
CompletableFuture - miért jó a
ForkJoinPoolCPU-bound felbontási munkára, de miért kockázatos blocking workloadra
2. Alapfogalmak
2.1 `Executor` és `ExecutorService`
Az Executor a minimális absztrakció:
- futtasd ezt a taskot valahogy
Az ExecutorService ehhez hozzáadja:
- a submission API-kat
- a shutdown API-kat
- a
Futureeredményeket - a bulk task metódusokat
A fontos design kérdés nem csak az, hogy melyik factory metódust hívod.
Hanem az is, hogy:
- hány thread van?
- milyen queue típust használsz?
- mi történik overload alatt?
2.1.1 Kifejezések és szerződések, amelyeket itt érdemes kimondani
Ezek a fontos contract szavak ebben a témában:
Executor— minimális task execution absztrakcióExecutorService— lifecycle-lal és result handlinggel rendelkező executorThreadPoolExecutor— konfigurálható pool implementáció- queue capacity — backlog határ, ahol megjelenik a nyomás vagy a rejection
- rejection policy — mi történik, ha a pool már nem tud több munkát fogadni
Future— pending result handle- cancellation — kérés a futó task megszakítására vagy az eredmény eldobására
CompletableFuture— kompozícionálható aszinkron stage APIForkJoinPool— work-stealing pool finomszemcsés taskokhoz- work stealing — idle workerek más workerektől lopnak taskot
- common pool — megosztott alapértelmezett
ForkJoinPool shutdown()— graceful, több taskot már nem fogadó leállításshutdownNow()— erősebb, interruption-orientált leállítási kísérlet
2.2 `Future` versus `CompletableFuture`
A Future akkor hasznos, ha kell:
- egy pending eredmény
- blokkoló várakozás
get()-tel - cancellation lehetőség
A gyenge pontja a kompozíció.
Sok Future összefűzése gyakran egymásba ágyazott blokkolást vagy kényelmetlen orchestrationt eredményez.
A CompletableFuture függő stage-eket modellez.
Lehetővé teszi, hogy:
- eredményt transzformálj
- eredményeket kombinálj
- hibából visszaállj
- timeoutot alkalmazz
- aszinkron pipeline-t építs
2.3 Pool viselkedés és overload
A fix méretű pool unbounded queue-val kordában tartja a threadek számát.
De az overloadot elrejtheti így:
- növekvő latency
- növekvő queue memóriaigény
- késleltetett hiba
A cached vagy elastic pool csökkenti a queueinget.
Viszont burst alatt túl sok threadet hozhat létre.
Ezért a queue policy nem implementációs részlet.
Ez a service contract része.
2.4 `ForkJoinPool`
A ForkJoinPool rekurzív felbontásra és finomszemcsés CPU-bound feladatokra optimalizált.
A workerek saját deque-et tartanak fenn.
Az idle worker másoktól tud taskot lopni.
Ez hatékony divide-and-conquer workloadnál.
Blocking I/O-heavy munkára többnyire rossz alapértelmezés, hacsak nem érted mélyen a trade-offokat.
3. Gyakorlati használat
Explicit pool ownership
Ha a komponens létrehoz egy poolt, akkor rendszerint a shutdown is az ő felelőssége.
Ha a pool megosztott infrastruktúra, a komponens ne zárja le csak úgy.
Ennek az ownership boundarynek kódban is egyértelműnek kell lennie.
Production-barát alapbeállítások
Amikor üzemeltetési viselkedés számít, érdemes explicit ThreadPoolExecutor konfigurációt használni.
Így meghatározhatod:
- a thread countot
- a queue típusát és méretét
- a thread naminget
- a rejection policyt
- a keep-alive viselkedést
A threadnév és a rejection policy nem opcionális díszítés.
Ezek az observability és overload behavior részei.
`CompletableFuture` fegyelem
A CompletableFuture erős eszköz.
De könnyű túlhasználni.
Minden aszinkron stage-nek legyen értelmes execution contextje.
Ha nem adsz meg executort, az async stage-ek gyakran a common poolt használják.
Ez könnyen összekever különböző workloadokat.
Graceful shutdown
A helyes leállítás tipikusan azt jelenti, hogy:
- nem fogadsz több új munkát
- megvárod a folyamatban levő taskokat
- csak szükség esetén eszkalálsz
A vakon meghívott shutdownNow() félbehagyott munkát eredményezhet.
4. Kód példák
1. példa: explicit thread pool
ThreadFactory factory = runnable -> {
Thread thread = new Thread(runnable);
thread.setName("pricing-worker-" + THREAD_ID.incrementAndGet());
return thread;
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(
8,
16,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(500),
factory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
Kulcspontok:
- explicit boundok
- elnevezett workerek
- explicit queue
- explicit rejection policy
2. példa: `Future` eredménykezelés
Future<Price> future = executor.submit(() -> pricingService.calculate(order));
Price price = future.get(200, TimeUnit.MILLISECONDS);
Kulcspontok:
- a
Futureegyetlen pending eredmény fogantyúja - a
get()blokkoló határpont - a timeout gyakran a caller contract része
3. példa: `CompletableFuture` kompozíció
CompletableFuture<Customer> customerFuture = CompletableFuture
.supplyAsync(() -> customerClient.fetch(customerId), ioExecutor);
CompletableFuture<Balance> balanceFuture = CompletableFuture
.supplyAsync(() -> accountClient.fetchBalance(customerId), ioExecutor);
CompletableFuture<Summary> summaryFuture = customerFuture.thenCombine(
balanceFuture,
Summary::new
).orTimeout(300, TimeUnit.MILLISECONDS)
.exceptionally(ex -> Summary.fallback(customerId));
Summary summary = summaryFuture.join();
Kulcspontok:
- a független hívások egyszerre futhatnak
- a kombináció explicit
- a timeout és a fallback a pipeline része
4. példa: graceful shutdown
executor.shutdown();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
Kulcspontok:
- először graceful leállítás
- utána eszkaláció, ha kell
- a lifecycle explicit
5. Trade-offok
| Döntés | Előny | Költség vagy kockázat |
|---|---|---|
ExecutorService |
Jobb lifecycle és pooling, mint a raw thread | Sizing és ownership döntések kellenek |
| Unbounded queue | Nem nő kontroll nélkül a threadszám | Elrejtheti az overloadot latency és memória formájában |
| Bounded queue | Láthatóvá teszi a nyomást | Kell hozzá rejection stratégia |
Future |
Egyszerű pending result handle | Gyenge kompozíciós modell |
CompletableFuture |
Erős orchestration és recovery lehetőségek | Könnyű túlkomplikálni a flow-t |
ForkJoinPool |
Hatékony finomszemcsés CPU-munkára | Kockázatos blocking workloadnál |
Gyakorlati trade-off elemzés
A Concurrency API kontrollt ad.
De ezzel együtt döntéseket is kikényszerít.
Ezek a döntések production viselkedéssé válnak.
Ezért a jó válasz nem az, hogy:
- „ismerem az
ExecutorService-t”
Hanem az, hogy:
- „értem, milyen queueing, rejection és shutdown viselkedést választ a szolgáltatásom”
6. Gyakori hibák
1. hiba: convenience factory használata a pool viselkedés megértése nélkül
A factory metódusok kényelmesek.
Az üzemeltetési viselkedésük viszont nem mindig az, amit szeretnél.
Helyes megközelítés:
- értsd a threadszámot, queueinget és rejection policyt
2. hiba: a saját executor shutdownjának elfelejtése
Ez threadszivárgást és váratlan életben maradó folyamatokat okoz.
Helyes megközelítés:
- tedd explicitte az ownershipet és graceful módon állítsd le
3. hiba: a common pool blokkolása felelőtlenül
A megosztott pool éheztethet más, nem kapcsolódó munkákat.
Helyes megközelítés:
- blocking vagy üzletkritikus workloadhoz használj dedikált executort
4. hiba: úgy kezelni a `Future`-t, mintha jól kompozícionálható lenne
Nem az.
Helyes megközelítés:
- orchestration esetén inkább
CompletableFuture
5. hiba: aszinkron stage-ek szétszórása mindenhová
A tiszta határok nélküli aszinkronitás megnehezíti a tracinget és a hibakezelést.
Helyes megközelítés:
- ott használj async kompozíciót, ahol valódi concurrency vagy latency előny van
6. hiba: overload viselkedés figyelmen kívül hagyása
A stressz alatti poolnak is kell policy.
Helyes megközelítés:
- döntsd el, hogy queue-olni, rejectelni vagy visszanyomást adni akarsz-e a callernek
7. Mélymerülés
7.1 A queueing architektúra
Egy unbounded queue elsőre biztonságosnak tűnik, mert a taskokat elfogadja.
De a rejtett overload gyakran rosszabb, mint a látható rejection.
Ez a következőket eredményezheti:
- növekvő latency
- elöregedő munka
- memória-nyomás
- rossz tail viselkedés
7.2 Ownership és shutdown
Egy executor infrastruktúra-erőforrás.
Ezért a lifecycle-jának választ kell adnia arra, hogy:
- ki hozza létre?
- ki állítja le?
- mi történik a queued workkel?
- mi történik alkalmazásleálláskor?
Ha ezekre nincs világos válasz, abból incidens lesz.
7.3 `CompletableFuture` mint orchestration eszköz
A CompletableFuture akkor a legerősebb, amikor függőségi gráfokat fejez ki.
Gyengébb akkor, amikor csak „async mindenhová” módon használják.
Az érett válasz kiemeli:
- a stage kompozíciót
- a timeout elhelyezését
- a fallback határokat
- az executor választást
7.4 `ForkJoinPool` realizmus
A work stealing kiváló CPU-bound rekurzív workloadokra.
Nem varázslat.
Ha a taskok sokat blokkolnak, a throughput és fairness sérülhet.
Ezért gyenge válasz az, hogy „használjuk a common poolt mindenre”.
8. Interjúkérdések
1. Miért jobb az `ExecutorService`, mint a raw thread?
Mert szétválasztja a task submissiont az execution resource menedzsmenttől, és lifecycle kontrollt ad.
2. Mit reprezentál a `Future`?
Egy pending eredmény fogantyúját.
3. Miért jobb gyakran orchestrationre a `CompletableFuture`?
Mert sokkal tisztábban kompozícionálhatóak a függő aszinkron stage-ek.
4. Miért fontos a queue választás?
Mert a queueing megváltoztatja az overload viselkedést, a latencyt és a memóriahasználatot.
5. Mit csinál a rejection policy?
Meghatározza, mi történik, amikor az executor már nem tud több munkát elfogadni.
6. Miért fontos a thread naming poolokban?
Mert jobb diagnosztikát és observabilityt ad.
7. Mikor jó a `ForkJoinPool`?
Finomszemcsés CPU-bound rekurzív munkára.
8. Miért veszélyes a common pool blokkolása?
Mert a megosztott execution resource-on más taskok is éhezhetnek.
9. Mi egy jó shutdown sorrend saját executor esetén?
shutdown(), várakozás, majd szükség esetén eszkaláció.
10. Mi a senior szintű tanulság?
A pool viselkedése a rendszer szerződésének része, nem puszta implementációs részlet.
9. Szószedet
| Fogalom | Jelentés |
|---|---|
Executor |
Minimális task execution absztrakció |
ExecutorService |
Lifecycle-lal és result handlinggel rendelkező executor |
ThreadPoolExecutor |
Konfigurálható executor implementáció |
Future |
Pending eredmény fogantyúja |
CompletableFuture |
Kompozícionálható aszinkron stage API |
| rejection policy | Viselkedés, ha több munka már nem fogadható |
| bounded queue | Explicit kapacitáslimites queue |
| common pool | Megosztott alapértelmezett ForkJoinPool |
| work stealing | Az idle worker más workertől lop taskot |
| graceful shutdown | Új munka tiltása, miközben a folyamatban levő taskok befejeződnek |
10. Gyorsreferencia
- általános task dispatchre többnyire executor kell, nem raw thread
- a pool sizing és queueing architekturális döntés
- nevezd el a worker threadeket
- tedd explicitté a rejection viselkedést
- a
Futurejó egy pending eredményhez - a
CompletableFuturejó orchestrationre és recoveryre - ne blokkolj felelőtlenül a common poolon
- a saját executorokat explicit módon állítsd le
- overloadnál bounded erőforrásokkal gondolkodj
- interjún nevezd meg a queueing, rejection, ownership és shutdown trade-offokat
🎮 Játékok
10 kérdés