L’immutabilità è la risposta: quello che forse avete sottovalutato sulla gestione della concorrenza in Java 25

Se sviluppi in Java da qualche anno, ti sarai sicuramente imbattuto nei ThreadLocal. Che sia per memorizzare il contesto di sicurezza di un utente, una transazione di database o una sessione HTTP, i ThreadLocal sono stati per vent’anni lo standard di fatto per salvare dati legati al singolo thread di esecuzione. 

Con l’avvento di Java 25 (la nuova versione LTS) e la definitiva maturazione dei Virtual Threads, i ThreadLocal mostrano però tutti i loro limiti architetturali. 

Fortunatamente, la JEP 506 introduce la soluzione definitiva: gli Scoped Values. Scopriamo perché cambieranno il tuo modo di scrivere codice e come usarli da oggi. 


Il “lato oscuro” dei ThreadLocal

Per capire il valore degli Scoped Values, dobbiamo prima ricordare i tre grandi problemi strutturali dei ThreadLocal: 

  1. Mutabilità incontrollata: Qualsiasi metodo all’interno dello stesso thread può invocare .set() e modificare il dato. Questo rende il flusso del codice imprevedibile e difficile da debuggare. 
  2. Memory Leak dietro l’angolo: Un ThreadLocal mantiene i dati finché il thread è vivo o finché non si chiama esplicitamente .remove(). Se usi un Thread Pool (come in Tomcat o Quarkus), dimenticare un .remove() significa che i dati della richiesta dell’Utente A rimarranno in memoria e potrebbero essere letti accidentalmente dalla richiesta dell’Utente B. 
  3. L’impatto devastante sui Virtual Threads: Con i Virtual Threads possiamo lanciare milioni di thread contemporaneamente. Se ognuno di essi eredita la mappa dei ThreadLocal dal thread genitore (tramite InheritableThreadLocal), la memoria heap collassa in pochi secondi a causa delle continue copie dei dati. 

La svolta di Java 25: Cosa sono gli Scoped Values?

Gli Scoped Values (introdotti dalla JEP 506) nascono per risolvere questi problemi alla radice. Sono contenitori di dati progettati per essere: 

  • Immutabili: Una volta associato un valore, questo può essere solo letto, mai modificato. 
  • A tempo limitato (Scoped): Il valore esiste solo all’interno di un perimetro di codice ben definito (un blocco o una lambda). Una volta usciti da quel perimetro, il dato scompare da solo. 
  • Leggeri: La condivisione dei dati con i thread figli (compresi i Virtual Threads) non copia nulla in memoria, ma riutilizza la struttura esistente con un overhead quasi nullo. 

Scoped Values in Azione: Un esempio pratico

Vediamo come cambia il codice. Immaginiamo di voler passare il nome dell’utente autenticato attraverso i vari livelli della nostra applicazione (Controller -> Service -> Repository) senza doverlo passare come parametro in ogni singolo metodo. 

java

import java.lang.ScopedValue;

public class Application {

    // 1. Dichiarazione dello ScopedValue (tipicamente static e final)
    private static final ScopedValue<String> CONTESTO_UTENTE = ScopedValue.newInstance();

    public static void main(String[] args) {
        String utenteArrivato = "Alice";

        // Prima del blocco, lo ScopedValue è vuoto ("unbound")
        System.out.println("Prima: " + CONTESTO_UTENTE.orElse("Anonimo")); // Stampa: Anonimo

        // 2. Definiamo lo scope di validità del dato usando il pattern fluent
        ScopedValue.where(CONTESTO_UTENTE, utenteArrivato).run(() -> {
            
            // All'interno di questo blocco .run(), il valore è accessibile ovunque
            eseguiLogicaDiBusiness();
            
            // Funziona nativamente anche dentro i Virtual Threads figli!
            Thread.ofVirtual().start(() -> {
                System.out.println("[Virtual Thread] Lettura: " + CONTESTO_UTENTE.get()); // Stampa: Alice
            });

        });

        // 3. Una volta usciti dal blocco .run(), il dato viene rimosso automaticamente
        System.out.println("Dopo: " + CONTESTO_UTENTE.orElse("Anonimo")); // Stampa: Anonimo
    }

    private static void eseguiLogicaDiBusiness() {
        // Il metodo legge il valore in sicurezza senza bisogno di riceverlo come parametro
        if (CONTESTO_UTENTE.isBound()) 
            System.out.println("Esecuzione operazione per l'utente: " + CONTESTO_UTENTE.get()); // Stampa: Alice
        
    }
}

Usa il codice con cautela.

Perché questo codice è migliore?

Guardando l’esempio balzano subito all’occhio tre vantaggi enormi: 

  • Niente Memory Leak: Non c’è nessun blocco try-finally con all’interno un CONTESTO_UTENTE.remove(). Non serve. Appena la lambda passata a .run() termina, Java pulisce tutto automaticamente. 
  • Sicurezza del dato: Non esiste un metodo CONTESTO_UTENTE.set(). All’interno di eseguiLogicaDiBusiness(), nessuno sviluppatore potrà sovrascrivere accidentalmente il nome dell’utente. 
  • Integrazione perfetta con la concorrenza moderna: Il Virtual Thread figlio legge il contesto istantaneamente e senza sprecare byte di memoria RAM. 

Conclusioni

I ThreadLocal hanno fatto la storia di Java, ma l’era del cloud computing e della programmazione concorrente su larga scala richiede strumenti più robusti e sicuri. Con Java 25, gli Scoped Values si confermano la scelta standard per la gestione dei contesti di esecuzione. 

Se stai progettando un nuovo framework o aggiornando i tuoi servizi aziendali a Java 25, è il momento di iniziare la migrazione: il tuo codice sarà più pulito, più sicuro e decisamente più veloce. 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *