Interviu Java senior: concurență și limite
Pregătește interviul Java senior: fire virtuale, gestionarea executorului, timeout, anulare și limitele dependențelor, cu exemple și autoevaluare.
În acest material
O discuție Java de nivel senior verifică dacă poți lega codul concurent de limitele sistemului. Dacă ai zece mii de cereri și doar cincizeci de conexiuni disponibile, crearea mai multor fire nu creează automat capacitate în baza de date.
Ce pregătești la acest nivel
Ai nevoie de colecții, excepții, teste și o înțelegere a accesului simultan la date. Recapitulează Java mid, apoi studiază atomicitatea, vizibilitatea memoriei, anularea, limitele de timp și observabilitatea. Acest articol urmărește închiderea corectă a resurselor și limitele unui executor cu fire virtuale.
Exemplul folosește Java 21+, versiune în care API-ul utilizat este disponibil fără opțiuni preview. Nu promitem că aceeași configurare este optimă pentru orice serviciu. La interviu, confirmă mai întâi versiunea de Java, natura operațiilor și bugetul de resurse.
Exemplu: mai multe sarcini, fără stare partajată
Programul lansează câte o sarcină pentru fiecare argument și calculează lungimea lui. Este un exemplu de lifecycle al executorului, nu un benchmark și nici o recomandare de paralelizare a unor calcule atât de mici. Fiecare sarcină citește propriul text, fără să modifice o colecție comună.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<Integer>> results = new ArrayList<>();
for (String text : args) {
results.add(executor.submit(() -> text.length()));
}
for (Future<Integer> result : results) {
System.out.println(result.get());
}
}
}
}
Compilează fișierul Main.java cu javac Main.java, apoi rulează java Main java react. Rezultatul este 4, apoi 5, pe două linii. Sarcinile se pot termina în altă ordine, însă parcurgerea listei de rezultate păstrează ordinea argumentelor. String.length() numără unități UTF-16, nu neapărat caracterele percepute vizual; pentru acest exercițiu folosește argumente ASCII.
Blocul try închide executorul. Metoda close() așteaptă terminarea sarcinilor. Este o proprietate utilă, dar trebuie înțeleasă când sarcinile pot rămâne blocate. Comportamentul este documentat în ExecutorService.
Ce se schimbă pentru un serviciu real
Executorul creează un fir virtual pentru fiecare sarcină și nu impune singur o limită a numărului de fire. Firele virtuale sunt utile mai ales când multe operații așteaptă I/O. Ele nu măresc numărul nucleelor și nu accelerează automat calculele CPU. Contractul fabricii este descris în Executors.
Pentru o dependență lentă, stabilește un număr maxim de cereri în zbor, un termen pentru obținerea accesului și un timeout al apelului propriu-zis. Un semafor poate limita accesul la dependență, dar un număr nelimitat de cereri care așteaptă semaforul consumă tot resurse. Ai nevoie și de o politică de admitere sau respingere.
Future.get(timeout, unit) limitează așteptarea apelantului, nu oprește automat sarcina. cancel(true) cere întreruperea, însă sarcina și biblioteca de I/O trebuie să coopereze. Dacă resursa ignoră întreruperea, nici un bloc try nu garantează un răspuns în termen. Separă timeoutul de conectare, timeoutul citirii și termenul întregii operații.
Cum răspunzi unei situații de producție
Intervievatorul spune: „după creșterea traficului, latența explodează”. Cere date despre coada de așteptare, numărul operațiilor active, conexiunile disponibile și distribuția latenței. P95 înseamnă că 95% dintre observații sunt cel mult acea valoare; media singură poate ascunde cererile foarte lente.
Propune o ipoteză verificabilă, precum saturarea unei dependențe. Măsoară înainte și după o schimbare izolată. Nu crește simultan toate limitele: poți muta blocajul către baza de date și poți agrava incidentul. Definește și comportamentul vizibil pentru utilizator când serviciul refuză temporar cereri.
Exersează
- Explică de ce ieșirea exemplului este ordonată, deși execuția este concurentă. Răspuns: citim lista de
Futureîn ordinea inserării. - Desenează ce se întâmplă când un apel expiră, dar sarcina continuă. Marchează separat cererea utilizatorului, firul și conexiunea ocupată.
- Proiectează o limită de 20 de apeluri externe simultane. Include ce se întâmplă cu apelul 21, cât așteaptă și ce măsurători publici.
Autoevaluare și pasul următor
Poți continua dacă separi concurența de paralelism, timeoutul de anulare și numărul firelor de capacitatea dependențelor. Urmează Java arhitect: contracte și consistență.
Întrebări frecvente
Firele virtuale elimină race conditions?
Nu. Accesul simultan la stare mutabilă cere aceleași reguli de sincronizare sau o proiectare care evită partajarea.
De ce nu este exemplul un test de performanță?
Calculele sunt prea mici, iar programul verifică gestionarea sarcinilor. Un benchmark necesită metodologie și un workload reprezentativ.
Un timeout garantează eliberarea conexiunii?
Nu. Verifică anularea suportată de clientul folosit și închiderea resurselor, inclusiv când apare o excepție.
Este suficient să măresc poolul bazei de date?
Nu. Serverul bazei de date are propriile limite. Măsoară capacitatea reală și costul cererilor înainte să mărești concurența.
Senior înseamnă să cunosc fiecare clasă din concurrent?
Nu. Contează să explici garanțiile, riscurile și metoda de verificare, apoi să alegi un API potrivit problemei.
Surse și versiuni
Java 21+. Referințele API de mai sus au fost consultate la 12 octombrie 2026. Exemplul folosește numai API-uri stabile.