Há uma excelente explicação desse problema por Andrei Pangin , datada de 07 de abril de 2015. Está disponível aqui , mas está escrita em russo (sugiro revisar os exemplos de código de qualquer maneira - eles são internacionais). O problema geral é um bloqueio durante a inicialização da classe.
Aqui estão algumas citações do artigo:
De acordo com o JLS , cada classe possui um bloqueio de inicialização exclusivo que é capturado durante a inicialização. Quando outro encadeamento tenta acessar essa classe durante a inicialização, ele será bloqueado no bloqueio até que a inicialização seja concluída. Quando as classes são inicializadas simultaneamente, é possível obter um deadlock.
Escrevi um programa simples que calcula a soma de inteiros, o que deve imprimir?
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);
}
}
Agora remova parallel()
ou substitua lambda por Integer::sum
chamada - o que mudará?
Aqui, vemos o deadlock novamente [houve alguns exemplos de deadlocks em inicializadores de classe anteriormente no artigo]. Por causa das parallel()
operações de fluxo executadas em um conjunto de encadeamentos separado. Esses threads tentam executar o corpo lambda, que é escrito em bytecode como um private static
método dentro da StreamSum
classe. Mas este método não pode ser executado antes da conclusão do inicializador estático da classe, que aguarda os resultados da conclusão do fluxo.
O que é mais impressionante: este código funciona de maneira diferente em ambientes diferentes. Ele funcionará corretamente em uma máquina com uma única CPU e provavelmente travará em uma máquina com várias CPUs. Essa diferença vem da implementação do pool Fork-Join. Você pode verificar você mesmo alterando o parâmetro-Djava.util.concurrent.ForkJoinPool.common.parallelism=N