C'è un'eccellente spiegazione di questo problema di Andrei Pangin , datata 7 aprile 2015. È disponibile qui , ma è scritto in russo (suggerisco comunque di rivedere i campioni di codice - sono internazionali). Il problema generale è un blocco durante l'inizializzazione della classe.
Ecco alcune citazioni dall'articolo:
Secondo JLS , ogni classe ha un blocco di inizializzazione univoco che viene acquisito durante l'inizializzazione. Quando un altro thread tenta di accedere a questa classe durante l'inizializzazione, verrà bloccato sul blocco fino al completamento dell'inizializzazione. Quando le classi vengono inizializzate contemporaneamente, è possibile ottenere un deadlock.
Ho scritto un semplice programma che calcola la somma dei numeri interi, cosa dovrebbe stampare?
public class StreamSum {
static final int SUM = IntStream.range(0, 100).parallel().reduce((n, m) -> n + m).getAsInt();
public static void main(String[] args) {
System.out.println(SUM);
}
}
Ora rimuovi parallel()
o sostituisci lambda con Integer::sum
call: cosa cambierà?
Qui vediamo di nuovo il deadlock [c'erano alcuni esempi di deadlock negli inizializzatori di classe precedentemente nell'articolo]. A causa del parallel()
flusso, le operazioni vengono eseguite in un pool di thread separato. Questi thread tentano di eseguire lambda body, che è scritto in bytecode come private static
metodo all'interno della StreamSum
classe. Ma questo metodo non può essere eseguito prima del completamento dell'inizializzatore statico della classe, che attende i risultati del completamento del flusso.
Cosa c'è di più strabiliante: questo codice funziona in modo diverso in ambienti diversi. Funzionerà correttamente su una macchina con una singola CPU e molto probabilmente si bloccherà su una macchina con più CPU. Questa differenza deriva dall'implementazione del pool Fork-Join. Puoi verificarlo tu stesso modificando il parametro-Djava.util.concurrent.ForkJoinPool.common.parallelism=N