Concurență Structurată în Java: StructuredTaskScope, Subtask-uri și Propagarea Erorilor în Java

Ghid complet pentru Concurența Structurată în Java (JEP 453/480): gestionarea ciclului de viață al sarcinilor paralele cu StructuredTaskScope, anularea automată în cascadă cu ShutdownOnFailure și pattern-uri de cursă cu ShutdownOnSuccess.

Sintaxă
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { ... scope.join(); }

Concurența tradițională în Java (bazată pe ExecutorService și Future) este nestructurată: un task părinte poate lansa mai multe task-uri paralele, dar dacă părintele eșuează sau este anulat, sarcinile copil continuă să ruleze pe fundal ca procese orfane (provocând risipă de CPU, memorie și conexiuni de baze de date lăsate deschise).

Concurența Structurată (Structured Concurrency) tratează un grup de sarcini concurente ca pe o unitate atomică unică de lucru: relațiile părinte-copil sunt strict delimitate sintactic, iar erorile și anulările se propagă automat în cascadă.

Execuție Paralelă Sigură cu StructuredTaskScope.ShutdownOnFailure
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

record Comanda(String utilizator, double total, String adresaLivrare) {}

public class StructuredConcurrencyDemo {

    public Comanda preiaDateComanda(long comandaId) throws Exception {
        // ShutdownOnFailure: dacă oricare dintre subtask-uri aruncă o excepție,
        // celelalte subtask-uri active sunt anulate automat pe loc!
        try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
            
            // Lansăm două interogări concurente:
            Subtask taskUtilizator = scope.fork(() -> interogheazaServiciuUtilizatori(comandaId));
            Subtask taskTotal = scope.fork(() -> calculeazaTotalFactura(comandaId));

            // Așteptăm ca ambele sarcini să se finalizeze sau ca una să eșueze:
            scope.join();
            scope.throwIfFailed(); // Propagă excepția primului subtask eșuat!

            // Dacă am ajuns aici, ambele sarcini au garantat succes:
            return new Comanda(taskUtilizator.get(), taskTotal.get(), "București, Sector 1");
        } // La ieșirea din blocul try, toate resursele rămase sunt curățate determinist!
    }

    private String interogheazaServiciuUtilizatori(long id) { return "Dexter"; }
    private double calculeazaTotalFactura(long id) { return 450.75; }
}

Pattern-ul „Primul Răspuns Câștigă” cu ShutdownOnSuccess

Dacă doriți să interogați simultan mai multe surse redundante (de exemplu două noduri dintr-un cluster sau două servere DNS) și să preluați primul rezultat valid:

try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
    scope.fork(() -> interogheazaServerOglindaA());
    scope.fork(() -> interogheazaServerOglindaB());

    scope.join();
    String rezultatRapid = scope.result(); // Returnează valoarea primului care a terminat; celălalt este anulat!
}