0Pricing
Java Academy · Lezione

Sicurezza dei thread ed effetti collaterali

Evitare lo stato mutabile condiviso

Sicurezza dei thread ed effetti collaterali è una lezione Java Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Java Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Java Academy include 4 lezioni in totale.

Il parallelismo implica accessi concorrenti

In uno stream parallelo, più thread eseguono contemporaneamente le lambda. Qualsiasi stato mutabile condiviso da esse utilizzato diventa una condizione di race.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int safe = IntStream.rangeClosed(1, 1000).parallel().sum();
        System.out.println(safe);
    }
}

Il pericolo: modificare una variabile condivisa

Un semplice contatore int utilizzato da più thread perde degli aggiornamenti. Non accumuli mai dati in uno stato mutabile condiviso all'interno di forEach.

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        AtomicInteger counter = new AtomicInteger();
        IntStream.rangeClosed(1, 1000).parallel()
            .forEach(n -> counter.incrementAndGet());
        System.out.println(counter.get());
    }
}

Perché le collezioni normali non funzionano

Aggiungere elementi a un ArrayList non thread-safe da un forEach parallelo può corromperlo o causare la perdita di elementi. La soluzione è non condividere e modificare lo stato.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> result = IntStream.rangeClosed(1, 10).parallel()
            .boxed()
            .collect(Collectors.toList());
        System.out.println(result.size());
    }
}

Lo strumento giusto: collect

Usi collect invece di forEach con effetti collaterali. Il framework dei collector accumula in modo sicuro contenitori parziali per ogni thread e li unisce.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        List<Integer> squares = IntStream.rangeClosed(1, 6).parallel()
            .map(n -> n * n)
            .boxed()
            .collect(Collectors.toList());
        System.out.println(squares);
    }
}

Lo strumento giusto: reduce

reduce è intrinsecamente thread-safe quando l'operazione è associativa e priva di stato, perché unisce risultati parziali senza modificare uno stato condiviso.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int sum = IntStream.rangeClosed(1, 1000).parallel()
            .reduce(0, Integer::sum);
        System.out.println(sum);
    }
}

Lambda senza stato

Le lambda negli stream dovrebbero essere prive di stato: il loro risultato dipende solo dall'input, non da variabili mutabili esterne o dagli elementi precedenti.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long count = IntStream.rangeClosed(1, 1000).parallel()
            .filter(n -> n % 2 == 0)
            .count();
        System.out.println(count);
    }
}

I tipi atomici come soluzione alternativa

Se deve davvero condividere un contatore, usi classi atomiche come AtomicLong. Serializzano correttamente gli aggiornamenti, anche se aggiungono contesa.

import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        AtomicLong sum = new AtomicLong();
        IntStream.rangeClosed(1, 1000).parallel()
            .forEach(sum::addAndGet);
        System.out.println(sum.get());
    }
}

Collezioni concorrenti

Quando serve una mappa condivisa tra thread, ConcurrentHashMap è sicura. Tuttavia, quando possibile, preferisca Collectors.groupingByConcurrent.

import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("a", "bb", "cc", "ddd");
        Map<Integer, List<String>> byLen = words.parallelStream()
            .collect(Collectors.groupingByConcurrent(String::length));
        System.out.println(byLen);
    }
}

Eviti la logica dipendente dall'ordine

Non faccia affidamento sull'ordine di attraversamento all'interno delle lambda parallele. Le operazioni che presuppongono un'elaborazione sequenziale producono risultati errati in parallelo.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int sum = IntStream.rangeClosed(1, 100).parallel()
            .map(n -> n * 2)
            .sum();
        System.out.println(sum);
    }
}

Le funzioni pure si parallelizzano in sicurezza

La regola d'oro è scrivere operazioni pure, prive di stato e non interferenti. In questo modo uno stream può essere sequenziale o parallelo e produrre risultati identici e corretti.

import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> in = List.of("red", "green", "blue");
        String out = in.parallelStream()
            .map(String::toUpperCase)
            .sorted()
            .collect(Collectors.joining(","));
        System.out.println(out);
    }
}

Assenza di interferenze

Non modifichi la collezione sorgente mentre uno stream la attraversa. Questo causa ConcurrentModificationException o un comportamento non definito, sia in sequenza sia in parallelo.

import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> src = List.of(1, 2, 3, 4);
        List<Integer> doubled = src.parallelStream()
            .map(n -> n * 2)
            .collect(Collectors.toList());
        System.out.println(doubled);
    }
}

Verifica rapida

Qual è il modo consigliato per accumulare i risultati di uno stream parallelo?

Riepilogo

Ha mantenuto corretti gli stream paralleli:

  • Lo stato mutabile condiviso nelle lambda parallele causa race condition.
  • Preferisca collect e reduce a forEach con effetti collaterali.
  • Mantenga le lambda prive di stato, pure e non interferenti.
  • Se deve condividere lo stato, usi tipi atomici o collezioni concorrenti, ma consideri questa una soluzione di ultima istanza.

Domande Frequenti

La lezione «Sicurezza dei thread ed effetti collaterali» è gratuita?

Sì — il testo completo di «Sicurezza dei thread ed effetti collaterali» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Java Academy, passa a CoddyKit PRO. Il corso Java Academy include 4 lezioni in totale.

Cosa imparerò in «Sicurezza dei thread ed effetti collaterali»?

Evitare lo stato mutabile condiviso Eserciti Java Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Java Academy?

Non è richiesta alcuna esperienza precedente. Java Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Sicurezza dei thread ed effetti collaterali»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Java Academy?

Sì. Ogni lezione Java Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Creazione di stream paralleli
  2. Quando il parallelismo è utile
  3. Sicurezza dei thread ed effetti collaterali
  4. Problemi comuni
← Torna a Java Academy