Setiap kali sebuah pertanyaan muncul di SO tentang sinkronisasi Java, beberapa orang sangat ingin menunjukkan bahwa synchronized(this)
harus dihindari. Sebaliknya, mereka mengklaim, kunci pada referensi pribadi lebih disukai.
Beberapa alasan yang diberikan adalah:
- beberapa kode jahat dapat mencuri kunci Anda (sangat populer yang ini, juga memiliki varian "tidak sengaja")
- semua metode yang disinkronkan dalam kelas yang sama menggunakan kunci yang sama persis, yang mengurangi throughput
- Anda (tidak perlu) mengekspos terlalu banyak informasi
Orang lain, termasuk saya, berpendapat bahwa synchronized(this)
ini adalah ungkapan yang banyak digunakan (juga di perpustakaan Jawa), aman dan dipahami dengan baik. Seharusnya tidak dihindari karena Anda memiliki bug dan Anda tidak memiliki petunjuk tentang apa yang terjadi di program multithreaded Anda. Dengan kata lain: jika itu berlaku, maka gunakanlah.
Saya tertarik melihat beberapa contoh dunia nyata (tidak ada hal foobar) di mana menghindari kunci this
lebih disukai ketika synchronized(this)
juga akan melakukan pekerjaan.
Karena itu: haruskah Anda selalu menghindari synchronized(this)
dan menggantinya dengan kunci pada referensi pribadi?
Beberapa info lebih lanjut (diperbarui saat jawaban diberikan):
- kita berbicara tentang sinkronisasi contoh
- baik implisit (
synchronized
metode) dan bentuk eksplisitsynchronized(this)
dianggap - jika Anda mengutip Bloch atau otoritas lain tentang masalah ini, jangan tinggalkan bagian yang tidak Anda sukai (mis. Java Efektif, item tentang Keamanan Thread: Biasanya itu adalah kunci pada instance itu sendiri, tetapi ada pengecualian.)
- jika Anda membutuhkan rincian dalam penguncian selain dari
synchronized(this)
penyediaan, makasynchronized(this)
tidak berlaku sehingga tidak menjadi masalah
Jawaban:
Saya akan membahas setiap poin secara terpisah.
Saya lebih khawatir tentang tidak sengaja . Jumlahnya adalah bahwa penggunaan ini
this
adalah bagian dari antarmuka kelas Anda yang terbuka, dan harus didokumentasikan. Terkadang kemampuan kode lain untuk menggunakan kunci Anda diinginkan. Ini berlaku untuk hal-hal sepertiCollections.synchronizedMap
(lihat javadoc).Ini adalah pemikiran yang terlalu sederhana; hanya menyingkirkan
synchronized(this)
tidak akan menyelesaikan masalah. Sinkronisasi yang tepat untuk throughput akan membutuhkan lebih banyak pemikiran.Ini adalah varian dari # 1. Penggunaan
synchronized(this)
adalah bagian dari antarmuka Anda. Jika Anda tidak ingin / butuh ini terbuka, jangan lakukan itu.sumber
keySet()
danvalues()
tidak mengunci (mereka)this
, tetapi contoh peta, yang penting untuk mendapatkan perilaku yang konsisten untuk semua operasi peta. Alasannya, objek kunci difaktorkan ke dalam variabel, adalah, bahwa subclassSynchronizedSortedMap
membutuhkannya untuk mengimplementasikan sub-peta yang mengunci pada instance peta asli.Yah, pertama-tama harus ditunjukkan bahwa:
secara semantik setara dengan:
yang merupakan salah satu alasan untuk tidak menggunakan
synchronized(this)
. Anda mungkin berpendapat bahwa Anda dapat melakukan hal-hal di sekitarsynchronized(this)
blok. Alasan yang biasa adalah untuk mencoba dan menghindari harus melakukan pemeriksaan yang disinkronkan sama sekali, yang mengarah ke semua jenis masalah konkurensi, khususnya masalah penguncian dua kali lipat , yang hanya menunjukkan betapa sulitnya membuat pemeriksaan yang relatif sederhana threadsafe.Kunci pribadi adalah mekanisme pertahanan, yang tidak pernah merupakan ide yang buruk.
Juga, seperti yang Anda singgung, kunci pribadi dapat mengontrol granularity. Satu set operasi pada suatu objek mungkin sama sekali tidak terkait dengan yang lain tetapi
synchronized(this)
akan saling mengecualikan akses ke semuanya.synchronized(this)
hanya benar-benar tidak memberi Anda apa-apa.sumber
Saat Anda menggunakan sinkronisasi (ini), Anda menggunakan instance kelas sebagai kunci itu sendiri. Ini berarti bahwa sementara kunci diperoleh oleh utas 1 , utas 2 harus menunggu.
Misalkan kode berikut:
Metode 1 memodifikasi variabel a dan metode 2 memodifikasi variabel b , modifikasi bersamaan dari variabel yang sama dengan dua utas harus dihindari dan itu. TETAPI sementara thread1 memodifikasi a dan memodifikasi thread2 b itu dapat dilakukan tanpa kondisi balapan.
Sayangnya, kode di atas tidak akan mengizinkan ini karena kami menggunakan referensi yang sama untuk kunci; Ini berarti bahwa utas bahkan jika mereka tidak dalam kondisi balapan harus menunggu dan jelas kode mengorbankan konkurensi program.
Solusinya adalah menggunakan 2 kunci berbeda untuk dua variabel berbeda:
Contoh di atas menggunakan lebih banyak kunci berbutir halus (2 kunci, bukan satu ( lockA dan lockB masing-masing untuk variabel a dan b ) dan sebagai hasilnya memungkinkan konkurensi yang lebih baik, di sisi lain itu menjadi lebih kompleks daripada contoh pertama ...
sumber
Sementara saya setuju untuk tidak mengikuti aturan dogmatis secara membabi buta, apakah skenario "pencurian kunci" tampak begitu eksentrik bagi Anda? Utas memang dapat memperoleh kunci pada objek Anda "eksternal" (
synchronized(theObject) {...}
), memblokir utas lainnya menunggu metode instance yang disinkronkan.Jika Anda tidak percaya pada kode berbahaya, pertimbangkan bahwa kode ini dapat berasal dari pihak ketiga (misalnya jika Anda mengembangkan semacam server aplikasi).
Versi "tidak disengaja" tampaknya kurang mungkin, tetapi seperti yang mereka katakan, "membuat sesuatu yang idiot-proof dan seseorang akan menciptakan idiot yang lebih baik".
Jadi saya setuju dengan itu-tergantung-pada-apa-yang-kelas-sekolah pemikiran.
Edit 3 komentar pertama eljenso berikut:
Saya tidak pernah mengalami masalah pencurian kunci tetapi ini adalah skenario imajiner:
Katakanlah sistem Anda adalah wadah servlet, dan objek yang kami pertimbangkan adalah
ServletContext
implementasinya. ItsgetAttribute
metode harus benang-aman, sebagai atribut konteks adalah data bersama; jadi Anda menyatakannya sebagaisynchronized
. Mari kita bayangkan juga Anda menyediakan layanan hosting publik berdasarkan pada implementasi wadah Anda.Saya adalah pelanggan Anda dan menyebarkan servlet "baik" saya di situs Anda. Kebetulan kode saya berisi panggilan ke
getAttribute
.Seorang hacker, yang menyamar sebagai pelanggan lain, menyebarkan servlet jahatnya di situs Anda. Ini berisi kode berikut dalam
init
metode:Dengan asumsi kita berbagi konteks servlet yang sama (diizinkan oleh spesifikasi selama dua servlet berada di host virtual yang sama), panggilan saya aktif
getAttribute
terkunci selamanya. Peretas telah mencapai DoS pada servlet saya.Serangan ini tidak mungkin jika
getAttribute
disinkronkan pada kunci pribadi, karena kode pihak ketiga tidak dapat memperoleh kunci ini.Saya akui bahwa contohnya dibuat dan pandangan yang terlalu sederhana tentang bagaimana wadah servlet bekerja, tetapi IMHO itu membuktikan intinya.
Jadi saya akan membuat pilihan desain berdasarkan pertimbangan keamanan: akankah saya memiliki kendali penuh atas kode yang memiliki akses ke instance? Apa yang akan menjadi konsekuensi dari utas yang memegang kunci pada instance tanpa batas?
sumber
Itu tergantung situasi.
Jika Hanya ada satu entitas berbagi atau lebih dari satu.
Lihat contoh kerja lengkap di sini
Pengantar kecil.
Utas dan entitas yang dapat dibagikan
Dimungkinkan untuk beberapa utas untuk mengakses entitas yang sama, misalnya untuk beberapa koneksi. Benang berbagi satu pesan tunggal. Karena utas berjalan secara bersamaan mungkin ada kemungkinan mengesampingkan data seseorang dengan yang lain yang mungkin menjadi situasi kacau.
Jadi kita perlu beberapa cara untuk memastikan bahwa entitas yang dapat dibagikan diakses hanya oleh satu utas pada satu waktu. (CONCURRENCY).
Blok
disinkronkan () blok disinkronkan adalah cara untuk memastikan akses bersamaan dari entitas yang dapat dibagikan.
Pertama, analogi kecil
Misalkan Ada dua orang P1, P2 (utas) sebuah Baskom (entitas yang dapat dibagikan) di dalam kamar mandi dan ada pintu (kunci).
Sekarang kami ingin satu orang menggunakan wastafel sekaligus.
Suatu pendekatan adalah dengan mengunci pintu oleh P1 ketika pintu dikunci P2 menunggu sampai p1 menyelesaikan pekerjaannya
P1 membuka kunci pintu
maka hanya p1 yang dapat menggunakan wastafel.
sintaksis.
"this" menyediakan kunci intrinsik yang terkait dengan kelas (pengembang Java mendesain kelas Object sedemikian rupa sehingga setiap objek dapat berfungsi sebagai monitor). Pendekatan di atas berfungsi dengan baik ketika hanya ada satu entitas bersama dan beberapa utas (1: N). N entitas yang dapat dibagikan - M thread Sekarang pikirkan situasi ketika ada dua wastafel di dalam kamar mandi dan hanya satu pintu. Jika kita menggunakan pendekatan sebelumnya, hanya p1 yang dapat menggunakan satu wastafel sekaligus, sementara p2 akan menunggu di luar. Ini pemborosan sumber daya karena tidak ada yang menggunakan B2 (wastafel). Pendekatan yang lebih bijaksana adalah menciptakan ruang yang lebih kecil di dalam kamar kecil dan menyediakan satu pintu untuk setiap wastafel. Dengan cara ini, P1 dapat mengakses B1 dan P2 dapat mengakses B2 dan sebaliknya.
Lihat lebih lanjut tentang Utas ----> di sini
sumber
Tampaknya ada konsensus yang berbeda di kamp-kamp C # dan Java mengenai hal ini. Mayoritas kode Java yang saya lihat menggunakan:
sedangkan mayoritas kode C # memilih yang lebih aman:
Idi C # tentu saja lebih aman. Seperti disebutkan sebelumnya, tidak ada akses jahat / tidak sengaja ke kunci dapat dilakukan dari luar mesin virtual. Kode Java juga memiliki risiko ini, tetapi tampaknya komunitas Java cenderung mengikuti versi yang sedikit kurang aman, tetapi sedikit lebih singkat.
Itu tidak dimaksudkan sebagai penggalian terhadap Jawa, hanya cerminan pengalaman saya bekerja pada kedua bahasa.
sumber
The
java.util.concurrent
paket telah jauh berkurang kompleksitas benang kode saya aman. Saya hanya memiliki bukti anekdotal untuk melanjutkan, tetapi sebagian besar pekerjaan yang saya lihatsynchronized(x)
tampaknya menerapkan kembali Kunci, Semaphore, atau Latch, tetapi menggunakan monitor tingkat bawah.Dengan mengingat hal ini, sinkronisasi menggunakan salah satu mekanisme ini analog dengan sinkronisasi pada objek internal, daripada membocorkan kunci. Ini bermanfaat karena Anda memiliki kepastian mutlak bahwa Anda mengontrol entri ke monitor dengan dua utas atau lebih.
sumber
final
variabel)Lock
API granular ]Kode sampel untuk digunakan
ReentrantLock
yang mengimplementasikanLock
antarmukaKeuntungan dari Lock over Synchronized (ini)
Penggunaan metode atau pernyataan yang disinkronkan memaksa semua akuisisi dan pelepasan kunci terjadi dengan cara terstruktur-blok.
Implementasi kunci menyediakan fungsionalitas tambahan atas penggunaan metode dan pernyataan yang disinkronkan dengan menyediakan
tryLock()
)lockInterruptibly()
)tryLock(long, TimeUnit)
).Kelas Lock juga dapat memberikan perilaku dan semantik yang sangat berbeda dari kunci monitor implisit, seperti
Lihatlah pertanyaan SE ini mengenai berbagai jenis
Locks
:Sinkronisasi vs Kunci
Anda dapat mencapai keamanan utas dengan menggunakan API konkurensi canggih alih-alih blok yang disinkronkan. Halaman dokumentasi ini menyediakan konstruksi pemrograman yang baik untuk mencapai keamanan utas.
Lock Objects mendukung idiom pengunci yang menyederhanakan banyak aplikasi bersamaan.
Pelaksana menetapkan API tingkat tinggi untuk meluncurkan dan mengelola utas. Implementasi pelaksana yang disediakan oleh java.util.concurrent menyediakan manajemen kumpulan thread yang cocok untuk aplikasi skala besar.
Koleksi Bersamaan membuatnya lebih mudah untuk mengelola koleksi data yang besar, dan dapat sangat mengurangi kebutuhan untuk sinkronisasi.
Variabel Atom memiliki fitur yang meminimalkan sinkronisasi dan membantu menghindari kesalahan konsistensi memori.
ThreadLocalRandom (dalam JDK 7) menyediakan generasi nomor pseudorandom yang efisien dari berbagai utas.
Mengacu pada java.util.concurrent dan java.util.concurrent.atomic paket juga untuk konstruksi pemrograman lainnya.
sumber
Jika Anda telah memutuskan bahwa:
maka saya tidak melihat tabu atas sinkronisasi (ini).
Beberapa orang sengaja menggunakan sinkronisasi (ini) (alih-alih menandai metode yang disinkronkan) di dalam seluruh isi metode karena mereka pikir itu "lebih jelas bagi pembaca" di mana objek sebenarnya disinkronkan. Selama orang-orang membuat pilihan berdasarkan informasi (mis. Pahami bahwa dengan melakukan hal itu mereka benar-benar memasukkan bytecodes tambahan ke dalam metode dan ini bisa berdampak pada optimisasi potensial), saya tidak terlalu melihat masalah dengan ini . Anda harus selalu mendokumentasikan perilaku bersamaan dari program Anda, jadi saya tidak melihat argumen "'disinkronkan' menerbitkan perilaku" sebagai hal yang begitu meyakinkan.
Mengenai pertanyaan tentang kunci objek mana yang harus Anda gunakan, saya pikir tidak ada yang salah dengan menyinkronkan objek saat ini jika ini akan diharapkan oleh logika dari apa yang Anda lakukan dan bagaimana kelas Anda biasanya akan digunakan . Misalnya, dengan koleksi, objek yang secara logis Anda harapkan akan dikunci adalah koleksi itu sendiri.
sumber
Saya pikir ada penjelasan yang baik tentang mengapa masing-masing teknik penting di bawah ikat pinggang Anda dalam sebuah buku berjudul Java Concurrency In Practice oleh Brian Goetz. Dia membuat satu titik sangat jelas - Anda harus menggunakan kunci yang sama "DI MANA SAJA" untuk melindungi keadaan objek Anda. Metode yang disinkronkan dan sinkronisasi pada suatu objek sering berjalan seiring. Misalnya Vektor menyinkronkan semua metodenya. Jika Anda memiliki pegangan untuk objek vektor dan akan melakukan "menempatkan jika tidak ada" maka hanya menyinkronkan vektor metode sendiri tidak akan melindungi Anda dari korupsi negara. Anda perlu melakukan sinkronisasi menggunakan sinkronisasi (vectorHandle). Ini akan menghasilkan kunci SAMA yang diperoleh oleh setiap utas yang memiliki pegangan pada vektor dan akan melindungi keseluruhan keadaan vektor. Ini disebut penguncian sisi klien. Kita tahu bahwa sebenarnya vektor tidak disinkronkan (ini) / menyinkronkan semua metode dan karenanya sinkronisasi pada objek vectorHandle akan menghasilkan sinkronisasi yang tepat dari keadaan objek vektor. Adalah bodoh untuk percaya bahwa Anda aman karena hanya menggunakan koleksi aman. Inilah tepatnya alasan ConcurrentHashMap secara eksplisit memperkenalkan metode putIfAbsent - untuk membuat operasi seperti itu menjadi atom.
Singkatnya
sumber
Tidak, kamu seharusnya tidak selalu . Namun, saya cenderung menghindarinya ketika ada beberapa kekhawatiran pada objek tertentu yang hanya perlu di-threadsafe untuk diri mereka sendiri. Misalnya, Anda mungkin memiliki objek data yang dapat diubah yang memiliki bidang "label" dan "induk"; ini harus threadsafe, tetapi mengubah yang satu tidak perlu menghalangi yang lain untuk ditulis / dibaca. (Dalam praktiknya saya akan menghindari ini dengan mendeklarasikan bidang volatil dan / atau menggunakan pembungkus AtomicFoo java.util.concurrent).
Sinkronisasi pada umumnya agak canggung, karena menampar kunci besar ke bawah daripada memikirkan dengan tepat bagaimana thread dapat dibiarkan saling bekerja sama. Menggunakannya
synchronized(this)
bahkan lebih klumsier dan anti-sosial, karena mengatakan "tidak ada yang dapat mengubah apa pun di kelas ini sementara saya memegang kunci". Seberapa sering Anda benar-benar perlu melakukan itu?Saya lebih suka memiliki kunci granular; bahkan jika Anda ingin menghentikan segala sesuatu dari perubahan (mungkin Anda membuat serial objek), Anda dapat memperoleh semua kunci untuk mencapai hal yang sama, ditambah lebih eksplisit seperti itu. Saat Anda menggunakan
synchronized(this)
, tidak jelas mengapa Anda melakukan sinkronisasi, atau apa efek sampingnya. Jika Anda menggunakansynchronized(labelMonitor)
, atau bahkan lebih baiklabelLock.getWriteLock().lock()
, jelas apa yang Anda lakukan dan apa efek dari bagian kritis Anda terbatas.sumber
Jawaban singkat : Anda harus memahami perbedaannya dan membuat pilihan tergantung pada kodenya.
Jawaban panjang : Secara umum saya lebih suka menghindari sinkronisasi (ini) untuk mengurangi pertikaian tetapi kunci pribadi menambah kerumitan yang harus Anda perhatikan. Jadi gunakan sinkronisasi yang tepat untuk pekerjaan yang tepat. Jika Anda tidak begitu berpengalaman dengan pemrograman multi-threaded saya lebih suka tetap mengunci misalnya dan membaca tentang topik ini. (Yang mengatakan: hanya menggunakan sinkronisasi (ini) tidak secara otomatis membuat kelas Anda sepenuhnya thread-safe.) Ini bukan topik yang mudah tetapi setelah Anda terbiasa, jawaban apakah akan menggunakan sinkronisasi (ini) atau tidak datang secara alami .
sumber
Kunci digunakan untuk visibilitas atau untuk melindungi beberapa data dari modifikasi bersamaan yang dapat menyebabkan balapan.
Ketika Anda hanya perlu membuat operasi tipe primitif menjadi atom ada pilihan yang tersedia seperti
AtomicInteger
dan suka.Tetapi misalkan Anda memiliki dua bilangan bulat yang saling terkait seperti
x
dany
koordinat, yang saling terkait dan harus diubah secara atom. Maka Anda akan melindungi mereka menggunakan kunci yang sama.Kunci seharusnya hanya melindungi keadaan yang terkait satu sama lain. Tidak kurang dan tidak lebih. Jika Anda menggunakan
synchronized(this)
dalam setiap metode maka bahkan jika keadaan kelas tidak terkait semua utas akan menghadapi pertentangan bahkan jika memperbarui keadaan tidak terkait.Dalam contoh di atas saya hanya memiliki satu metode yang bermutasi baik
x
dany
bukan dua metode yang berbedax
dany
terkait dan jika saya telah memberikan dua metode yang berbeda untuk bermutasix
dany
secara terpisah maka itu tidak akan aman thread.Contoh ini hanya untuk menunjukkan dan tidak harus cara penerapannya. Cara terbaik untuk melakukannya adalah membuatnya IMMUTABLE .
Sekarang berlawanan dengan
Point
contoh, ada contohTwoCounters
sudah disediakan oleh @ Andreas di mana negara yang dilindungi oleh dua kunci yang berbeda karena negara tidak terkait satu sama lain.Proses menggunakan kunci yang berbeda untuk melindungi keadaan yang tidak terkait disebut Lock Striping atau Lock Splitting
sumber
Alasan untuk tidak menyinkronkan ini adalah bahwa kadang-kadang Anda membutuhkan lebih dari satu kunci (kunci kedua sering dihapus setelah beberapa pemikiran tambahan, tetapi Anda masih membutuhkannya dalam kondisi peralihan). Jika Anda mengunci ini , Anda harus selalu ingat yang mana dari dua kunci ini ; Jika Anda mengunci objek pribadi, nama variabel memberitahu Anda itu.
Dari sudut pandang pembaca, jika Anda melihat mengunci ini , Anda selalu harus menjawab dua pertanyaan:
Sebuah contoh:
Jika dua utas dimulai
longOperation()
pada dua contoh berbedaBadObject
, mereka mendapatkan kunci mereka; ketika saatnya untuk memohonl.onMyEvent(...)
, kita memiliki jalan buntu karena tidak ada utas yang dapat memperoleh kunci objek lain.Dalam contoh ini kita dapat menghilangkan kebuntuan dengan menggunakan dua kunci, satu untuk operasi pendek dan satu untuk yang lama.
sumber
BadObject
A memanggillongOperation
B, melewati AmyListener
, dan sebaliknya. Bukan tidak mungkin, tetapi cukup berbelit-belit, mendukung poin saya sebelumnya.Seperti yang sudah dikatakan di sini, blok yang disinkronkan dapat menggunakan variabel yang ditentukan pengguna sebagai objek kunci, ketika fungsi yang disinkronkan hanya menggunakan "ini". Dan tentu saja Anda dapat memanipulasi dengan area fungsi Anda yang harus disinkronkan dan sebagainya.
Tetapi semua orang mengatakan bahwa tidak ada perbedaan antara fungsi yang disinkronkan dan blok yang mencakup seluruh fungsi menggunakan "ini" sebagai objek kunci. Itu tidak benar, perbedaannya adalah dalam kode byte yang akan dihasilkan dalam kedua situasi. Dalam hal penggunaan blok tersinkronisasi harus dialokasikan variabel lokal yang memegang referensi untuk "ini". Dan sebagai hasilnya kita akan memiliki ukuran fungsi yang sedikit lebih besar (tidak relevan jika Anda hanya memiliki sedikit fungsi).
Penjelasan lebih rinci tentang perbedaan ini dapat Anda temukan di sini: http://www.artima.com/insidejvm/ed2/threadsynchP.html
Penggunaan blok tersinkronisasi juga tidak baik karena sudut pandang berikut:
Untuk detail lebih lanjut dalam bidang ini, saya sarankan Anda membaca artikel ini: http://java.dzone.com/articles/synchronized-considered
sumber
Ini benar-benar hanya tambahan untuk jawaban yang lain, tetapi jika keberatan utama Anda untuk menggunakan objek pribadi untuk mengunci adalah bahwa hal itu mengacaukan kelas Anda dengan bidang yang tidak terkait dengan logika bisnis, maka Proyek Lombok harus
@Synchronized
membuat boilerplate pada waktu kompilasi:kompilasi ke
sumber
Contoh yang baik untuk penggunaan yang disinkronkan (ini).
Seperti yang dapat Anda lihat di sini, kami menggunakan sinkronisasi pada ini untuk memudahkan bekerja sama secara panjang (mungkin loop tak terbatas dari metode yang dijalankan) dengan beberapa metode yang disinkronkan di sana.
Tentu saja dapat dengan mudah ditulis ulang dengan menggunakan sinkronisasi pada bidang pribadi. Tetapi kadang-kadang, ketika kita sudah memiliki beberapa desain dengan metode disinkronkan (yaitu kelas warisan, kita berasal dari, disinkronkan (ini) dapat menjadi satu-satunya solusi).
sumber
this
. Itu bisa menjadi bidang pribadi.Tergantung pada tugas yang ingin Anda lakukan, tetapi saya tidak akan menggunakannya. Juga, periksa apakah thread-save-ness yang ingin Anda temani tidak dapat dilakukan dengan menyinkronkan (ini) sejak awal? Ada juga beberapa kunci bagus di API yang mungkin membantu Anda :)
sumber
Saya hanya ingin menyebutkan solusi yang mungkin untuk referensi pribadi yang unik di bagian kode atom tanpa ketergantungan. Anda dapat menggunakan Hashmap statis dengan kunci dan metode statis sederhana bernama atomic () yang membuat referensi yang diperlukan secara otomatis menggunakan informasi tumpukan (nama kelas lengkap dan nomor baris). Kemudian Anda dapat menggunakan metode ini dalam menyinkronkan pernyataan tanpa menulis objek kunci baru.
sumber
Hindari penggunaan
synchronized(this)
sebagai mekanisme penguncian: Ini mengunci seluruh instance kelas dan dapat menyebabkan deadlock. Dalam kasus seperti itu, refactor kode untuk mengunci hanya metode atau variabel tertentu, sehingga seluruh kelas tidak terkunci.Synchronised
dapat digunakan di dalam level metode.Alih-alih menggunakan
synchronized(this)
, kode di bawah ini menunjukkan bagaimana Anda bisa mengunci metode.sumber
Dua sen saya pada 2019 meskipun pertanyaan ini sudah bisa diselesaikan.
Mengunci 'ini' tidak buruk jika Anda tahu apa yang Anda lakukan tetapi di belakang layar mengunci 'ini' adalah (yang sayangnya kata kunci yang disinkronkan dalam definisi metode memungkinkan).
Jika Anda benar-benar ingin pengguna kelas Anda dapat 'mencuri' kunci Anda (mis. Mencegah utas lain untuk menanganinya), Anda sebenarnya ingin semua metode yang disinkronkan menunggu saat metode sinkronisasi lain berjalan dan seterusnya. Itu harus disengaja dan dipikirkan dengan baik (dan karenanya didokumentasikan untuk membantu pengguna Anda memahaminya).
Untuk lebih rumit, sebaliknya Anda harus tahu apa yang Anda 'dapatkan' (atau 'hilang') jika Anda mengunci kunci yang tidak dapat diakses (tidak ada yang bisa 'mencuri' kunci Anda, Anda berada dalam kontrol penuh dan sebagainya. ..)
Masalahnya bagi saya adalah bahwa kata kunci yang disinkronkan dalam metode definisi tanda tangan membuatnya terlalu mudah bagi programmer untuk tidak memikirkan apa yang harus dikunci yang merupakan hal penting yang hebat untuk dipikirkan jika Anda tidak ingin mengalami masalah dalam multi - Program yang terinstal.
Seseorang tidak dapat berargumen bahwa 'biasanya' Anda tidak ingin pengguna kelas Anda dapat melakukan hal-hal ini atau 'biasanya' yang Anda inginkan ... Itu tergantung pada fungsionalitas apa yang Anda koding. Anda tidak dapat membuat aturan praktis karena Anda tidak dapat memprediksi semua kasus penggunaan.
Pertimbangkan untuk misalnya penulis cetak yang menggunakan kunci internal tetapi kemudian orang-orang kesulitan untuk menggunakannya dari banyak utas jika mereka tidak ingin hasil mereka interleave.
Haruskah kunci Anda dapat diakses di luar kelas atau tidak adalah keputusan Anda sebagai programmer berdasarkan fungsi apa yang dimiliki kelas. Itu adalah bagian dari api. Anda tidak dapat pindah misalnya dari disinkronkan (ini) ke disinkronkan (provateObjet) tanpa risiko melanggar perubahan dalam kode yang menggunakannya.
Catatan 1: Saya tahu Anda dapat mencapai apa pun yang disinkronkan (ini) 'mencapai' dengan menggunakan objek kunci eksplisit dan mengeksposnya tapi saya pikir itu tidak perlu jika perilaku Anda didokumentasikan dengan baik dan Anda benar-benar tahu apa artinya mengunci 'ini'.
Catatan 2: Saya tidak setuju dengan argumen bahwa jika beberapa kode secara tidak sengaja mencuri kunci Anda, ini adalah bug dan Anda harus menyelesaikannya. Ini adalah argumen yang sama dengan mengatakan bahwa saya dapat membuat semua metode saya publik bahkan jika mereka tidak dimaksudkan untuk umum. Jika seseorang 'secara tidak sengaja' memanggil saya yang dimaksudkan sebagai metode pribadi, ini adalah bug. Mengapa mengaktifkan kecelakaan ini sejak awal !!! Jika kemampuan mencuri kunci Anda adalah masalah bagi kelas Anda, jangan izinkan. Sesimpel itu.
sumber
Saya pikir poin satu (orang lain menggunakan kunci Anda) dan dua (semua metode menggunakan kunci yang sama tidak perlu) dapat terjadi pada aplikasi yang cukup besar. Terutama ketika tidak ada komunikasi yang baik antar pengembang.
Itu tidak dilempar batu, itu sebagian besar masalah praktik yang baik dan mencegah kesalahan.
sumber