Sinkronisasi vs Kunci

182

java.util.concurrentAPI menyediakan kelas yang disebut Lock, yang pada dasarnya akan membuat serial kontrol untuk mengakses sumber daya kritis. Ini memberikan metode seperti park()dan unpark().

Kita bisa melakukan hal serupa jika kita dapat menggunakan synchronizedkata kunci dan menggunakan wait()dan notify() notifyAll()metode.

Saya bertanya-tanya mana yang lebih baik dalam praktiknya dan mengapa?

pelamun
sumber
1
artikel bermanfaat di sini: javarevisited.blogspot.in/2013/03/…
roottraveller

Jawaban:

178

Jika Anda hanya mengunci objek, saya lebih suka menggunakan synchronized

Contoh:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

Anda harus secara eksplisit melakukannya di try{} finally{}mana - mana.

Sedangkan dengan disinkronkan, sangat jelas dan tidak mungkin salah:

synchronized(myObject) {
    doSomethingNifty();
}

Yang mengatakan, Locks mungkin lebih berguna untuk hal-hal yang lebih rumit di mana Anda tidak dapat memperoleh dan melepaskan sedemikian bersih. Jujur saya lebih suka untuk menghindari menggunakan telanjang Lockdi tempat pertama, dan hanya pergi dengan kontrol konkurensi yang lebih canggih seperti CyclicBarrieratau LinkedBlockingQueue,, jika mereka memenuhi kebutuhan Anda.

Saya tidak pernah punya alasan untuk menggunakan wait()atau notify()tetapi mungkin ada beberapa yang bagus.

Steven Schlansker
sumber
1
Apa perbedaan antara menunggu / memberi tahu vs parkir / tidak parkir LockSupport? docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
Pacerier
6
Pada awalnya contoh masuk akal dengan kunci tetapi kemudian saya menyadari bahwa jika Anda menggunakan mencoba akhirnya memblokir masalah itu akan dihindari dengan kunci tidak dilepaskan
William Reed
Ahh ... Salah satu momen untuk mengapresiasi model RAII di C ++. std::lock_guard
WhiZTiM
67

Saya bertanya-tanya mana yang lebih baik dalam praktiknya dan mengapa?

Saya telah menemukan itu Lockdan Condition(dan concurrentkelas baru lainnya ) hanyalah lebih banyak alat untuk kotak alat. Saya dapat melakukan hampir semua yang saya butuhkan dengan palu cakar saya yang lama ( synchronizedkata kunci), tetapi rasanya canggung untuk digunakan dalam beberapa situasi. Beberapa dari situasi yang canggung itu menjadi jauh lebih mudah setelah saya menambahkan lebih banyak alat ke dalam kotak alat saya: palu karet, palu ball-peen, prybar, dan beberapa pukulan paku. Namun , palu cakar saya yang lama masih melihat penggunaannya.

Saya tidak berpikir satu benar-benar "lebih baik" dari yang lain, tetapi masing-masing lebih cocok untuk masalah yang berbeda. Singkatnya, model sederhana dan sifat berorientasi lingkup synchronizedmembantu melindungi saya dari bug dalam kode saya, tetapi keuntungan yang sama kadang-kadang menjadi hambatan dalam skenario yang lebih kompleks. Ini skenario yang lebih kompleks bahwa paket bersamaan dibuat untuk membantu mengatasi. Tetapi menggunakan konstruksi tingkat yang lebih tinggi ini memerlukan manajemen kode yang lebih eksplisit dan hati-hati.

===

Saya pikir JavaDoc melakukan pekerjaan yang baik untuk menggambarkan perbedaan antara Lockdan synchronized(penekanannya adalah milik saya):

Implementasi kunci menyediakan operasi penguncian yang lebih luas daripada yang bisa diperoleh dengan menggunakan metode dan pernyataan yang disinkronkan. Mereka memungkinkan penataan yang lebih fleksibel , mungkin memiliki sifat yang sangat berbeda, dan dapat mendukung beberapa objek Kondisi terkait .

...

Penggunaan metode disinkronkan atau pernyataan menyediakan akses ke kunci memantau implisit terkait dengan setiap objek, tetapi pasukan semua akuisisi kunci dan rilis terjadi dengan cara blok-terstruktur : ketika beberapa kunci yang diperoleh mereka harus dibebaskan dalam urutan yang berlawanan , dan semua kunci harus dilepaskan dalam lingkup leksikal yang sama di mana mereka diperoleh .

Meskipun mekanisme pelingkupan untuk metode dan pernyataan yang disinkronkan membuat program lebih mudah dengan kunci monitor , dan membantu menghindari banyak kesalahan pemrograman umum yang melibatkan kunci, ada saat-saat di mana Anda perlu bekerja dengan kunci dengan cara yang lebih fleksibel. Sebagai contoh, * * beberapa algoritma * untuk melintasi struktur data yang diakses secara bersamaan membutuhkan penggunaan "hand-over-hand" atau "penguncian rantai" : Anda memperoleh kunci simpul A, lalu simpul B, lalu lepaskan A dan dapatkan C, kemudian lepaskan B dan dapatkan D dan seterusnya. Implementasi antarmuka Kunci memungkinkan penggunaan teknik-teknik tersebut dengan memungkinkan kunci diperoleh dan dirilis dalam lingkup yang berbeda , danmemungkinkan beberapa kunci diperoleh dan dirilis dalam urutan apa pun .

Dengan peningkatan fleksibilitas ini muncul tanggung jawab tambahan . Tidak adanya penguncian terstruktur-blok menghilangkan pelepasan kunci secara otomatis yang terjadi dengan metode dan pernyataan yang tersinkronisasi. Dalam kebanyakan kasus, idiom berikut harus digunakan:

...

Ketika penguncian dan pembukaan kunci terjadi dalam cakupan yang berbeda , kehati-hatian harus diambil untuk memastikan bahwa semua kode yang dijalankan saat kunci dipegang dilindungi oleh try-akhirnya atau coba-tangkap untuk memastikan bahwa kunci dilepaskan ketika diperlukan.

Implementasi kunci memberikan fungsionalitas tambahan atas penggunaan metode dan pernyataan yang disinkronkan dengan memberikan upaya non-pemblokiran untuk mendapatkan kunci (tryLock ()), upaya untuk mendapatkan kunci yang dapat terganggu (lockInterruptibly (), dan upaya untuk memperoleh kunci yang dapat habis (tryLock (panjang, TimeUnit)).

...

Bert F
sumber
23

Anda dapat mencapai segala sesuatu utilitas di java.util.concurrent lakukan dengan primitif tingkat rendah seperti synchronized, volatileatau menunggu / memberitahukan

Namun, konkurensi itu rumit, dan kebanyakan orang mendapatkan setidaknya sebagiannya salah, membuat kode mereka salah atau tidak efisien (atau keduanya).

API bersamaan menyediakan pendekatan tingkat yang lebih tinggi, yang lebih mudah (dan karena itu lebih aman) untuk digunakan. Singkatnya, Anda tidak perlu menggunakan synchronized, volatile, wait, notifysecara langsung lagi.

Kelas Lock itu sendiri ada di sisi level bawah kotak alat ini, Anda bahkan mungkin tidak perlu menggunakannya secara langsung (Anda dapat menggunakan Queuesdan Semaphore dan sebagainya, dll, sebagian besar waktu).

Thilo
sumber
2
Apakah tunggu lama / pemberitahuan dianggap primitif tingkat lebih rendah dari java.util.concurrent.locks.LockSupport park / unpark, atau apakah sebaliknya?
Pacerier
@Pacerier: Saya menganggap keduanya sebagai level rendah (yaitu sesuatu yang ingin dihindari oleh programmer aplikasi secara langsung), tetapi tentu saja bagian level bawah dari java.util.concurrency (seperti paket kunci) dibangun di atas dari primitif JVM asli menunggu / memberi tahu (yang bahkan lebih rendah).
Thilo
2
Tidak maksud saya dari 3: Thread.sleep / interrupt, Object.wait / notify, LockSupport.park / unpark, yang mana yang paling primitif?
Pacerier
2
@Thilo Saya tidak yakin bagaimana Anda mendukung pernyataan Anda yang java.util.concurrentlebih mudah [secara umum] daripada fitur bahasa ( synchronized, dll ...). Ketika Anda menggunakan java.util.concurrentAnda harus membiasakan menyelesaikan lock.lock(); try { ... } finally { lock.unlock() }sebelum menulis kode sedangkan dengan synchronizedAnda pada dasarnya baik-baik saja dari awal. Atas dasar ini saja saya akan mengatakan synchronizedlebih mudah (mengingat Anda ingin perilakunya) daripada java.util.concurrent.locks.Lock. par 4
Iwan Aucamp
1
Jangan berpikir Anda bisa meniru perilaku kelas AtomicXXX hanya dengan primitif konkurensi, karena mereka bergantung pada doa CAS asli yang tidak tersedia sebelum java.util.concurrent.
Duncan Armstrong
15

Ada 4 faktor utama mengapa Anda ingin menggunakan synchronizedatau java.util.concurrent.Lock.

Catatan: Penguncian yang disinkronkan adalah yang saya maksud ketika saya mengatakan penguncian intrinsik.

  1. Ketika Java 5 keluar dengan ReentrantLocks, mereka terbukti memiliki perbedaan throughput perbedaan yang cukup dengan penguncian intrinsik. Jika Anda mencari mekanisme penguncian yang lebih cepat dan sedang menjalankan 1.5 pertimbangkan jucReentrantLock. Penguncian intrinsik Java 6 sekarang dapat dibandingkan.

  2. jucLock memiliki mekanisme penguncian yang berbeda. Lock interruptable - upaya untuk mengunci sampai utas pengunci terputus; kunci waktunya - upaya untuk mengunci selama waktu tertentu dan menyerah jika Anda tidak berhasil; tryLock - upaya untuk mengunci, jika ada utas lain yang menahan kunci menyerah. Ini semua disertakan selain dari kunci sederhana. Penguncian intrinsik hanya menawarkan penguncian sederhana

  3. Gaya. Jika kedua 1 dan 2 tidak termasuk dalam kategori apa yang Anda khawatirkan dengan kebanyakan orang, termasuk saya, akan menemukan semen pengunci intrinsik lebih mudah dibaca dan kurang verbose daripada penguncian jucLock.
  4. Berbagai Kondisi. Objek yang Anda kunci hanya dapat diberi tahu dan menunggu satu kasing. Metode newCondition Lock memungkinkan Lock tunggal untuk memiliki beberapa alasan untuk menunggu atau memberi sinyal. Saya belum benar-benar membutuhkan fungsi ini dalam praktek, tetapi merupakan fitur yang bagus untuk mereka yang membutuhkannya.
John Vint
sumber
Saya menyukai detail di komentar Anda. Saya akan menambahkan satu poin tambahan - ReadWriteLock memberikan perilaku yang berguna jika Anda berurusan dengan beberapa utas, hanya beberapa yang perlu menulis ke objek. Beberapa utas dapat secara bersamaan membaca objek dan hanya diblokir jika utas lain sudah menulisnya.
Sam Goldberg
5

Saya ingin menambahkan beberapa hal lagi di atas jawaban Bert F.

Locksmendukung berbagai metode untuk kontrol kunci berbutir halus, yang lebih ekspresif daripada monitor implisit ( synchronizedkunci)

Kunci menyediakan akses eksklusif ke sumber daya bersama: hanya satu utas pada satu waktu yang dapat memperoleh kunci dan semua akses ke sumber daya bersama mengharuskan kunci diperoleh terlebih dahulu. Namun, beberapa kunci dapat memungkinkan akses bersamaan ke sumber daya bersama, seperti kunci baca dari ReadWriteLock.

Keuntungan dari Sinkronisasi Terkunci dari halaman dokumentasi

  1. Penggunaan metode atau pernyataan yang disinkronkan memberikan akses ke kunci monitor implisit yang terkait dengan setiap objek, tetapi memaksa semua akuisisi dan pelepasan kunci terjadi dengan cara terstruktur-blok

  2. Implementasi kunci memberikan fungsionalitas tambahan atas penggunaan metode dan pernyataan yang disinkronkan dengan memberikan upaya non-pemblokiran untuk memperoleh lock (tryLock()), upaya untuk mendapatkan kunci yang dapat diinterupsi ( lockInterruptibly(), dan upaya untuk mendapatkan kunci yang bisa timeout (tryLock(long, TimeUnit)).

  3. Kelas kunci juga dapat memberikan perilaku dan semantik yang sangat berbeda dari kunci monitor implisit, seperti pemesanan terjamin, penggunaan non-reentran, atau deteksi kebuntuan

ReentrantLock : Dalam istilah sederhana sesuai pemahaman saya, ReentrantLockmemungkinkan objek masuk kembali dari satu bagian kritis ke bagian kritis lainnya. Karena Anda sudah memiliki kunci untuk memasuki satu bagian penting, Anda dapat bagian penting lainnya pada objek yang sama dengan menggunakan kunci saat ini.

ReentrantLockfitur utama sesuai artikel ini

  1. Kemampuan untuk mengunci secara interupsi.
  2. Kemampuan untuk kehabisan waktu sambil menunggu kunci.
  3. Kekuatan untuk membuat kunci yang adil.
  4. API untuk mendapatkan daftar utas kunci.
  5. Fleksibilitas untuk mencoba mengunci tanpa pemblokiran.

Anda dapat menggunakan ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLockuntuk mendapatkan kontrol lebih lanjut pada penguncian granular pada operasi baca dan tulis.

Terlepas dari tiga ReentrantLocks ini, java 8 menyediakan satu lagi Kunci

StampedLock:

Java 8 dikirimkan dengan jenis kunci baru yang disebut StampedLock yang juga mendukung kunci baca dan tulis seperti pada contoh di atas. Berbeda dengan ReadWriteLock, metode penguncian StampedLock mengembalikan perangko yang diwakili oleh nilai yang panjang.

Anda dapat menggunakan perangko ini untuk melepaskan kunci atau untuk memeriksa apakah kunci masih valid. Selain itu kunci yang dicap mendukung mode kunci lain yang disebut penguncian optimis.

Lihat artikel ini tentang penggunaan berbagai jenis ReentrantLockdan StampedLockkunci.

Ravindra babu
sumber
4

Perbedaan utama adalah keadilan, dengan kata lain apakah permintaan ditangani FIFO atau dapatkah ada tongkang? Sinkronisasi tingkat metode memastikan alokasi kunci atau FIFO adil. Menggunakan

synchronized(foo) {
}

atau

lock.acquire(); .....lock.release();

tidak menjamin keadilan.

Jika Anda memiliki banyak pertentangan untuk kunci tersebut, Anda dapat dengan mudah menemukan barging di mana permintaan yang lebih baru mendapatkan kunci dan permintaan yang lebih lama macet. Saya telah melihat kasus di mana 200 utas tiba dalam waktu singkat untuk kunci dan yang ke-2 tiba diproses terakhir. Ini ok untuk beberapa aplikasi tetapi untuk yang lain mematikan.

Lihat buku "Java Concurrency In Practice" karya Brian Goetz, bagian 13.3 untuk diskusi lengkap tentang topik ini.

Brian Tarbox
sumber
5
"Sinkronisasi tingkat metode memastikan alokasi kunci atau FIFO adil." => Benarkah? Apakah Anda mengatakan bahwa metode yang disinkronkan berperilaku berbeda wrt fairness daripada membungkus konten metode ke dalam blok {} yang disinkronkan? Saya tidak akan berpikir begitu, atau apakah saya memahami kalimat itu salah ...?
weiresr
Ya, meski mengejutkan dan berlawanan dengan intuisi itu benar. Buku Goetz adalah penjelasan terbaik.
Brian Tarbox
Jika Anda melihat kode yang disediakan oleh @BrianTarbox blok yang disinkronkan menggunakan beberapa objek selain "ini" untuk mengunci. Secara teori tidak ada perbedaan antara metode yang disinkronkan dan menempatkan seluruh tubuh metode tersebut di dalam blok yang disinkronkan, selama blok menggunakan "ini" sebagai kunci.
xburgos
Jawaban harus diedit untuk memasukkan kutipan, dan membuatnya jelas "jaminan" di sini adalah "jaminan statistik", bukan deterministik.
Nathan Hughes
Maaf, saya baru saja menemukan bahwa saya telah salah memilih jawaban ini beberapa hari yang lalu (mengklik canggung). Sayangnya, SO saat ini tidak akan membiarkan saya mengembalikannya. Semoga saya bisa memperbaikinya nanti.
Mikko Östlund
3

Buku "Java Concurrency In Practice" karya Brian Goetz, bagian 13.3: "... Seperti ReentrantLock default, penguncian intrinsik tidak memberikan jaminan keadilan deterministik, tetapi jaminan keadilan statistik dari sebagian besar implementasi penguncian cukup baik untuk hampir semua situasi ..."

bodrin
sumber
2

Kunci membuat hidup programmer lebih mudah. Berikut adalah beberapa situasi yang dapat dicapai dengan mudah dengan kunci.

  1. Kunci dalam satu metode, dan lepaskan kunci dalam metode lain.
  2. Jika Anda memiliki dua utas yang bekerja pada dua bagian kode yang berbeda, namun, di utas pertama memiliki prasyarat pada potongan kode tertentu di utas kedua (sementara beberapa utas lain juga bekerja pada bagian kode yang sama pada utas kedua). utas secara bersamaan). Kunci bersama dapat memecahkan masalah ini dengan cukup mudah.
  3. Mengimplementasikan monitor. Misalnya, antrian sederhana tempat metode put and get dieksekusi dari banyak utas lainnya. Namun, Anda tidak ingin beberapa metode put (atau mendapatkan) berjalan bersamaan, begitu juga metode put dan get berjalan secara bersamaan. Kunci pribadi membuat hidup Anda jauh lebih mudah untuk mencapai ini.

Sementara itu, kunci, dan kondisinya dibangun berdasarkan mekanisme tersinkronisasi. Karena itu, tentu saja dapat mencapai fungsi yang sama yang dapat Anda capai menggunakan kunci. Namun, menyelesaikan skenario kompleks dengan disinkronkan dapat membuat hidup Anda sulit dan dapat menyimpang Anda dari memecahkan masalah yang sebenarnya.

Md Monjur Ul Hasan
sumber
1

Perbedaan utama antara kunci dan disinkronkan:

  • dengan kunci, Anda dapat melepaskan dan memperoleh kunci dalam urutan apa pun.
  • dengan disinkronkan, Anda dapat melepaskan kunci hanya dalam urutan yang diperoleh.
NKumar
sumber
0

Mengunci dan menyinkronkan blok keduanya memiliki tujuan yang sama tetapi tergantung pada penggunaan. Pertimbangkan bagian di bawah ini

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

Dalam kasus di atas, jika utas memasuki blok sinkronisasi, blok lainnya juga dikunci. Jika ada beberapa blok sinkronisasi seperti itu pada objek yang sama, semua blok dikunci. Dalam situasi seperti itu, java.util.concurrent.Lock dapat digunakan untuk mencegah penguncian blok yang tidak diinginkan

Shyam
sumber