Bu sorunun mükemmel bir açıklaması, 07 Nisan 2015 tarihli Andrei Pangin tarafından yapılmıştır. Burada bulunabilir , ancak Rusça olarak yazılmıştır (yine de kod örneklerini incelemenizi öneririm - bunlar uluslararasıdır). Genel sorun, sınıf başlatma sırasında bir kilitlenmedir.
İşte makaleden bazı alıntılar:
JLS'ye göre , her sınıfın başlatma sırasında yakalanan benzersiz bir başlatma kilidi vardır. Başlatma sırasında diğer iş parçacığı bu sınıfa erişmeye çalıştığında, başlatma tamamlanana kadar kilit üzerinde engellenecektir. Sınıflar eşzamanlı olarak başlatıldığında, bir kilitlenme elde etmek mümkündür.
Tam sayıların toplamını hesaplayan basit bir program yazdım, neyi yazdırmalı?
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);
}
}
Şimdi parallel()
lambda'yı Integer::sum
call ile kaldırın veya değiştirin - ne değişecek?
Burada yine kilitlenme görüyoruz [makalede daha önce sınıf başlatıcılarında bazı kilitlenme örnekleri vardı]. parallel()
Akış işlemleri nedeniyle ayrı bir iş parçacığı havuzunda çalışır. Bu evreler, sınıf private static
içinde bir yöntem olarak bayt kodu ile yazılan lambda gövdesini çalıştırmaya çalışır StreamSum
. Ancak bu yöntem, akımın tamamlanmasının sonuçlarını bekleyen sınıf statik başlatıcısı tamamlanmadan çalıştırılamaz.
Daha akıllara durgunluk veren şey: bu kod farklı ortamlarda farklı şekilde çalışır. Tek bir CPU makinesinde doğru şekilde çalışacak ve büyük olasılıkla çok CPU'lu bir makinede asılı kalacaktır. Bu fark, Fork-Join havuzu uygulamasından gelir. Parametreyi değiştirerek kendiniz doğrulayabilirsiniz-Djava.util.concurrent.ForkJoinPool.common.parallelism=N