Ada penjelasan yang sangat bagus tentang masalah ini oleh Andrei Pangin , tertanggal 07 Apr 2015. Ini tersedia di sini , tetapi ditulis dalam bahasa Rusia (saya sarankan untuk meninjau contoh kode juga - mereka internasional). Masalah umum adalah kunci selama inisialisasi kelas.
Berikut beberapa kutipan dari artikel tersebut:
Menurut JLS , setiap kelas memiliki kunci inisialisasi unik yang ditangkap selama inisialisasi. Saat thread lain mencoba mengakses kelas ini selama inisialisasi, thread tersebut akan diblokir di kunci sampai inisialisasi selesai. Ketika kelas diinisialisasi secara bersamaan, mungkin saja terjadi kebuntuan.
Saya menulis program sederhana yang menghitung jumlah bilangan bulat, apa yang harus dicetak?
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);
}
}
Sekarang hapus parallel()
atau ganti lambda dengan Integer::sum
panggilan - apa yang akan berubah?
Di sini kita melihat kebuntuan lagi [ada beberapa contoh kebuntuan di penginisialisasi kelas sebelumnya di artikel]. Karena parallel()
operasi aliran dijalankan di kumpulan utas terpisah. Utas ini mencoba menjalankan lambda body, yang ditulis dalam bytecode sebagai private static
metode di dalam StreamSum
kelas. Tetapi metode ini tidak dapat dijalankan sebelum penyelesaian penginisialisasi statis kelas, yang menunggu hasil penyelesaian streaming.
Apa yang lebih mengejutkan: kode ini bekerja secara berbeda di lingkungan yang berbeda. Ini akan bekerja dengan benar pada satu mesin CPU dan kemungkinan besar akan digantung pada mesin multi CPU. Perbedaan ini berasal dari implementasi kumpulan Fork-Join. Anda dapat memverifikasi sendiri dengan mengubah parameter-Djava.util.concurrent.ForkJoinPool.common.parallelism=N