Ini adalah polling tentang masalah konkurensi umum di Jawa. Contoh mungkin kebuntuan klasik atau kondisi balapan atau mungkin bug threading EDT di Swing. Saya tertarik baik dalam berbagai kemungkinan masalah tetapi juga dalam masalah apa yang paling umum. Jadi, silakan tinggalkan satu jawaban spesifik bug konkurensi Java per komentar dan berikan suara jika Anda melihat yang Anda temui.
java
multithreading
concurrency
Alex Miller
sumber
sumber
Jawaban:
Masalah konkurensi paling umum yang pernah saya lihat, adalah tidak menyadari bahwa bidang yang ditulis oleh satu utas tidak dijamin dilihat oleh utas yang berbeda. Aplikasi umum ini:
Selama berhenti tidak stabil atau
setStop
danrun
tidak disinkronkan ini tidak dijamin untuk bekerja. Kesalahan ini terutama jahat karena pada 99,999% itu tidak masalah dalam praktik karena pembaca akhirnya akan melihat perubahan - tetapi kita tidak tahu seberapa cepat dia melihatnya.sumber
Saya # 1 yang paling menyakitkan masalah concurrency yang pernah terjadi ketika dua yang berbeda perpustakaan open source melakukan sesuatu seperti ini:
Sepintas, ini terlihat seperti contoh sinkronisasi yang cukup sepele. Namun; karena String diinternir di Jawa, string literal
"LOCK"
ternyata menjadi contoh yang samajava.lang.String
(meskipun mereka dinyatakan sepenuhnya berbeda satu sama lain.) Hasilnya jelas buruk.sumber
Satu masalah klasik adalah mengubah objek yang Anda sinkronkan saat menyinkronkannya:
Utas bersamaan lainnya kemudian disinkronkan pada objek yang berbeda dan blok ini tidak memberikan pengecualian bersama yang Anda harapkan.
sumber
Masalah umum adalah menggunakan kelas-kelas seperti Kalender dan SimpleDateFormat dari banyak utas (seringkali dengan menyimpannya dalam variabel statis) tanpa sinkronisasi. Kelas-kelas ini tidak aman thread sehingga akses multi-thread pada akhirnya akan menyebabkan masalah aneh dengan keadaan tidak konsisten.
sumber
Penguncian Ganda. Umumnya.
Paradigma, yang saya mulai pelajari dari masalah ketika saya bekerja di BEA, adalah bahwa orang akan memeriksa singleton dengan cara berikut:
Ini tidak pernah berhasil, karena utas lain mungkin telah masuk ke blok yang disinkronkan dan s_instance tidak lagi nol. Jadi perubahan alami adalah membuatnya:
Itu juga tidak berhasil, karena Java Memory Model tidak mendukungnya. Anda perlu mendeklarasikan s_instance sebagai volatile untuk membuatnya berfungsi, dan itupun hanya berfungsi di Java 5.
Orang yang tidak terbiasa dengan seluk-beluk Model Memori Java mengacaukan ini sepanjang waktu .
sumber
Tidak menyinkronkan dengan benar objek yang dikembalikan oleh
Collections.synchronizedXXX()
, terutama selama iterasi atau beberapa operasi:Itu salah . Meskipun ada satu operasi
synchronized
, keadaan peta antara memohoncontains
danput
dapat diubah oleh utas lainnya. Harus:Atau dengan
ConcurrentMap
implementasi:sumber
Meskipun mungkin tidak persis apa yang Anda minta, masalah terkait konkurensi yang paling sering saya temui (mungkin karena muncul dalam kode single-threaded normal) adalah
java.util.ConcurrentModificationException
disebabkan oleh hal-hal seperti:
sumber
Mungkin mudah untuk berpikir bahwa koleksi yang disinkronkan memberi Anda lebih banyak perlindungan daripada yang sebenarnya, dan lupa untuk memegang kunci di antara panggilan. Saya telah melihat kesalahan ini beberapa kali:
Misalnya, pada baris kedua di atas, metode
toArray()
dansize()
keduanya aman untuk masing-masing thread, tetapisize()
dievaluasi secara terpisah daritoArray()
, dan kunci pada Daftar tidak ditahan di antara dua panggilan ini.Jika Anda menjalankan kode ini dengan utas lain secara bersamaan menghapus item dari daftar, cepat atau lambat Anda akan berakhir dengan pengembalian baru
String[]
yang lebih besar dari yang diperlukan untuk menahan semua elemen dalam daftar, dan memiliki nilai nol di bagian ekor. Sangat mudah untuk berpikir bahwa karena dua metode panggilan ke Daftar terjadi dalam satu baris kode, ini entah bagaimana merupakan operasi atom, tetapi tidak.sumber
Bug paling umum yang kita lihat di tempat saya bekerja adalah programmer melakukan operasi panjang, seperti panggilan server, pada EDT, mengunci GUI selama beberapa detik dan membuat aplikasi tidak responsif.
sumber
Lupa menunggu () (atau Condition.await ()) dalam satu lingkaran, memeriksa bahwa kondisi menunggu sebenarnya benar. Tanpa ini, Anda mengalami bug dari wake () wakeups palsu. Penggunaan kanonik harus:
sumber
Bug umum lainnya adalah penanganan pengecualian yang buruk. Ketika utas latar belakang mengeluarkan pengecualian, jika Anda tidak menanganinya dengan benar, Anda mungkin tidak melihat jejak tumpukan sama sekali. Atau mungkin tugas latar belakang Anda berhenti berjalan dan tidak pernah mulai lagi karena Anda gagal menangani pengecualian.
sumber
Sampai aku mengambil kelas dengan Brian Goetz saya tidak menyadari bahwa non-disinkronkan
getter
dari bidang swasta bermutasi melalui disinkronkansetter
adalah tidak pernah dijamin untuk mengembalikan nilai diperbarui. Hanya ketika suatu variabel dilindungi oleh blok yang disinkronkan pada kedua tulisan DAN tulisan maka Anda akan mendapatkan jaminan nilai terbaru dari variabel tersebut.sumber
Mengira Anda sedang menulis kode single-threaded, tetapi menggunakan statika yang bisa berubah (termasuk lajang). Jelas mereka akan dibagikan di antara utas. Ini sering terjadi secara mengejutkan.
sumber
Panggilan metode sewenang-wenang tidak boleh dilakukan dari dalam blok yang disinkronkan.
Dave Ray menyentuh ini dalam jawaban pertamanya, dan pada kenyataannya saya juga menemui jalan buntu juga berkaitan dengan memohon metode pada pendengar dari dalam metode yang disinkronkan. Saya pikir pelajaran yang lebih umum adalah bahwa pemanggilan metode tidak boleh dibuat "ke alam" dari dalam blok yang disinkronkan - Anda tidak tahu apakah panggilan akan berjalan lama, mengakibatkan kebuntuan, atau apa pun.
Dalam kasus ini, dan biasanya secara umum, solusinya adalah mengurangi ruang lingkup blok yang disinkronkan untuk hanya melindungi bagian kode yang penting secara pribadi .
Juga, karena kami sekarang mengakses Koleksi pendengar di luar blok yang disinkronkan, kami mengubahnya menjadi Koleksi copy-on-write. Atau kita bisa saja membuat salinan Koleksi yang defensif. Intinya, biasanya ada alternatif untuk mengakses Koleksi benda-benda yang tidak dikenal dengan aman.
sumber
Bug yang berhubungan dengan Concurrency terbaru yang saya temui adalah sebuah objek yang dalam konstruktornya menciptakan ExecutorService, tetapi ketika objek tersebut tidak lagi direferensikan, itu tidak pernah mematikan ExecutorService. Jadi, selama beberapa minggu, ribuan utas bocor, akhirnya menyebabkan sistem macet. (Secara teknis, itu tidak crash, tapi itu berhenti berfungsi dengan baik, sambil terus berjalan.)
Secara teknis, saya kira ini bukan masalah konkurensi, tapi ini masalah yang berkaitan dengan penggunaan perpustakaan java.util.concurrency.
sumber
Sinkronisasi yang tidak seimbang, khususnya terhadap Maps tampaknya menjadi masalah yang cukup umum. Banyak orang percaya bahwa sinkronisasi on menempatkan ke Peta (bukan ConcurrentMap, tapi katakanlah HashMap) dan tidak disinkronkan saat mendapat sudah cukup. Namun ini dapat menyebabkan loop tak terbatas selama re-hash.
Masalah yang sama (sinkronisasi parsial) dapat terjadi di mana saja Anda telah berbagi keadaan dengan membaca dan menulis.
sumber
Saya mengalami masalah konkurensi dengan Servlets, ketika ada bidang yang bisa berubah yang akan diselesaikan oleh setiap permintaan. Tetapi hanya ada satu instance servlet untuk semua permintaan, jadi ini berfungsi dengan baik dalam lingkungan pengguna tunggal tetapi ketika lebih dari satu pengguna meminta hasil servlet yang tidak terduga terjadi.
sumber
Bukan bug tetapi, dosa terburuk adalah menyediakan perpustakaan yang Anda maksud orang lain gunakan, tetapi tidak menyatakan kelas / metode mana yang aman untuk thread dan mana yang hanya boleh dipanggil dari satu utas dll.
Lebih banyak orang harus menggunakan penjelasan konkurensi (mis. @ThreadSafe, @GuardedBy dll) yang dijelaskan dalam buku Goetz.
sumber
Masalah terbesar saya selalu kebuntuan, terutama disebabkan oleh pendengar yang dipecat dengan kunci dipegang. Dalam kasus ini, sangat mudah untuk mendapatkan penguncian terbalik antara dua utas. Dalam kasus saya, antara simulasi berjalan di satu utas dan visualisasi dari simulasi berjalan di utas UI.
EDIT: Pindah bagian kedua untuk memisahkan jawaban.
sumber
Memulai utas dalam konstruktor suatu kelas bermasalah. Jika kelas diperluas, utas dapat dimulai sebelum konstruktor subkelas dieksekusi.
sumber
Kelas yang dapat diubah dalam struktur data bersama
Ketika ini terjadi, kode jauh lebih kompleks dari contoh sederhana ini. Menggandakan, menemukan, dan memperbaiki bug itu sulit. Mungkin bisa dihindari jika kita bisa menandai kelas tertentu sebagai struktur data yang tidak dapat diubah dan tertentu sebagai hanya memegang objek yang tidak dapat diubah.
sumber
Sinkronisasi pada string literal atau konstan yang didefinisikan oleh string literal (berpotensi) merupakan masalah karena string literal diinternir dan akan dibagikan oleh siapa pun di JVM menggunakan string literal yang sama. Saya tahu masalah ini telah muncul di server aplikasi dan skenario "wadah" lainnya.
Contoh:
Dalam hal ini, siapa pun yang menggunakan string "foo" untuk mengunci berbagi kunci yang sama.
sumber
Saya percaya di masa depan masalah utama dengan Java adalah (kurangnya) jaminan visibilitas untuk konstruktor. Misalnya, jika Anda membuat kelas berikut
dan kemudian hanya membaca MyClass ini properti yang dari thread lain, MyClass.a bisa baik 0 atau 1, tergantung pada pelaksanaan dan suasana hati JavaVM ini. Saat ini peluang untuk 'a' menjadi 1 sangat tinggi. Tetapi pada mesin NUMA masa depan ini mungkin berbeda. Banyak orang tidak menyadari hal ini dan percaya bahwa mereka tidak perlu peduli multi-threading selama fase inisialisasi.
sumber
Kesalahan paling bodoh yang sering saya lakukan adalah lupa untuk menyinkronkan sebelum memanggil notify () atau wait () pada suatu objek.
sumber
Menggunakan "Objek baru ()" lokal sebagai mutex.
Ini tidak berguna.
sumber
Masalah 'konkurensi' lain yang umum adalah menggunakan kode yang disinkronkan ketika tidak diperlukan sama sekali. Sebagai contoh saya masih melihat programmer menggunakan
StringBuffer
atau bahkanjava.util.Vector
(sebagai metode variabel lokal).sumber
Beberapa objek yang dilindungi kunci tetapi biasanya diakses secara berurutan. Kami telah mengalami beberapa kasus di mana kunci diperoleh oleh kode yang berbeda dalam pesanan yang berbeda, yang mengakibatkan kebuntuan.
sumber
Tidak menyadari bahwa
this
kelas dalam bukanlahthis
kelas luar. Biasanya di kelas batin anonim yang mengimplementasikanRunnable
. Masalah mendasarnya adalah karena sinkronisasi adalah bagian dari semuaObject
s, maka secara efektif tidak ada pemeriksaan tipe statis. Saya telah melihat ini setidaknya dua kali di usenet, dan itu juga muncul di Brian Goetz'z Java Concurrency in Practice.Penutupan BGGA tidak menderita karena ini tidak ada
this
untuk penutupan (this
referensi kelas luar). Jika Anda menggunakan non-this
objek sebagai kunci maka itu mengatasi masalah ini dan yang lainnya.sumber
Penggunaan objek global seperti variabel statis untuk mengunci.
Hal ini menyebabkan kinerja yang sangat buruk karena pertikaian.
sumber
Honesly? Sebelum munculnya
java.util.concurrent
, masalah paling umum yang sering saya temui adalah apa yang saya sebut "meronta-ronta": Aplikasi yang menggunakan utas untuk konkurensi, tetapi menelurkan terlalu banyak dan akhirnya meronta-ronta.sumber