Mengapa menggunakan ReentrantLock jika seseorang dapat menggunakan sinkronisasi (ini)?

317

Saya mencoba memahami apa yang membuat kunci dalam konkurensi sangat penting jika seseorang dapat menggunakannya synchronized (this). Dalam kode dummy di bawah ini, saya dapat melakukan salah satu dari ini:

  1. menyinkronkan seluruh metode atau menyinkronkan area rentan ( synchronized(this){...})
  2. ATAU kunci area kode rentan dengan ReentrantLock.

Kode:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}
adhg
sumber
1
BTW semua kunci intrinsik java pada dasarnya adalah reentrant.
Aniket Thakur
@pongapundit begitu synchronized(this){synchronized(this){//some code}} tidak akan menyebabkan kunci mati. Untuk kunci intrinsik jika mereka mendapatkan monitor pada sumber daya dan jika mereka menginginkannya lagi mereka bisa mendapatkannya tanpa kunci mati.
Aniket Thakur

Jawaban:

474

Sebuah ReentrantLock adalah tidak terstruktur , tidak seperti synchronizedkonstruksi - yaitu Anda tidak perlu menggunakan struktur blok untuk mengunci dan bahkan dapat memegang kunci di seluruh metode. Sebuah contoh:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

Aliran seperti itu tidak mungkin untuk diwakili melalui monitor tunggal dalam suatu synchronizedkonstruksi.


Selain itu, ReentrantLockmendukung polling kunci dan kunci interruptible menunggu yang mendukung time-out . ReentrantLockjuga memiliki dukungan untuk kebijakan keadilan yang dapat dikonfigurasi , memungkinkan penjadwalan thread yang lebih fleksibel.

Konstruktor untuk kelas ini menerima parameter keadilan opsional . Ketika ditetapkantrue , di bawah pertentangan, kunci mendukung pemberian akses ke utas terlama. Kalau tidak, kunci ini tidak menjamin urutan akses tertentu. Program menggunakan kunci yang adil diakses oleh banyak utas mungkin menampilkan throughput keseluruhan yang lebih rendah (yaitu, lebih lambat; sering jauh lebih lambat) daripada yang menggunakan pengaturan default, tetapi memiliki varians yang lebih kecil di kali untuk mendapatkan kunci dan menjamin kurangnya kelaparan. Namun perlu dicatat bahwa keadilan kunci tidak menjamin keadilan penjadwalan utas. Dengan demikian, salah satu dari banyak utas yang menggunakan kunci adil dapat memperolehnya beberapa kali berturut-turut sementara utas aktif lainnya tidak mengalami kemajuan dan saat ini tidak memegang kunci. Perhatikan juga bahwa waktutryLockMetode tidak menghormati pengaturan keadilan. Ini akan berhasil jika kunci tersedia bahkan jika utas lain sedang menunggu.


ReentrantLock mungkin juga lebih skalabel , berkinerja jauh lebih baik di bawah pertengkaran yang lebih tinggi. Anda dapat membaca lebih lanjut tentang ini di sini .

Namun klaim ini telah diperdebatkan; lihat komentar berikut:

Dalam uji kunci reentrant, kunci baru dibuat setiap kali, sehingga tidak ada penguncian eksklusif dan data yang dihasilkan tidak valid. Juga, tautan IBM tidak menawarkan kode sumber untuk tolok ukur yang mendasarinya sehingga tidak mungkin untuk mengkarakterisasi apakah pengujian bahkan dilakukan dengan benar.


Kapan sebaiknya Anda gunakan ReentrantLock s? Menurut artikel developerWorks itu ...

Jawabannya cukup sederhana - gunakan ketika Anda benar-benar membutuhkan sesuatu yang tidak disediakannya synchronized, seperti menunggu kunci waktu, kunci menunggu interruptible, kunci non-blok-terstruktur, beberapa variabel kondisi, atau polling kunci. ReentrantLockjuga memiliki manfaat skalabilitas, dan Anda harus menggunakannya jika Anda benar-benar memiliki situasi yang menunjukkan pertikaian tinggi, tetapi ingat bahwa sebagian besar synchronizedblok hampir tidak pernah menunjukkan pertengkaran apa pun, apalagi pertengkaran tinggi. Saya akan menyarankan pengembangan dengan sinkronisasi sampai sinkronisasi terbukti tidak memadai, daripada hanya mengasumsikan "kinerja akan lebih baik" jika Anda menggunakanReentrantLock. Ingat, ini adalah alat canggih untuk pengguna tingkat lanjut. (Dan pengguna yang benar-benar mahir cenderung lebih suka alat paling sederhana yang dapat mereka temukan sampai mereka yakin alat sederhana tidak memadai.) Seperti biasa, perbaiki dulu, lalu khawatir apakah Anda harus membuatnya lebih cepat atau tidak.

oldrinb
sumber
26
Tautan 'diketahui lebih skalabel' ke lycog.com harus dihapus. Dalam uji kunci reentrant, kunci baru dibuat setiap kali, sehingga tidak ada penguncian eksklusif dan data yang dihasilkan tidak valid. Juga, tautan IBM tidak menawarkan kode sumber untuk tolok ukur yang mendasarinya sehingga tidak mungkin untuk mengkarakterisasi apakah pengujian bahkan dilakukan dengan benar. Secara pribadi, saya baru saja menghapus seluruh baris tentang skalabilitas, karena seluruh klaim pada dasarnya tidak didukung.
Dev
2
Saya mengubah pos sesuai dengan respons Anda.
oldrinb
6
Jika kinerjanya sangat memprihatinkan bagi Anda, jangan lupa mencari cara di mana Anda TIDAK perlu sinkronisasi sama sekali.
mcoolive
2
Hal kinerja tidak masuk akal bagi saya sama sekali. Jika kunci reantrant akan bekerja lebih baik maka mengapa tidak disinkronkan tidak hanya diterapkan dengan cara yang sama kunci reantrant secara internal?
tObi
2
@ user2761895 ReentrantLockPseudoRandomkode di tautan Lycog menggunakan kunci baru yang tidak dikunci setiap pemanggilan setSeeddannext
oldrinb
14

ReentrantReadWriteLockadalah kunci khusus sedangkan synchronized(this)kunci tujuan umum. Mereka serupa tetapi tidak persis sama.

Anda benar bahwa Anda bisa menggunakan synchronized(this)bukannya ReentrantReadWriteLocktetapi sebaliknya tidak selalu benar.

Jika Anda ingin lebih memahami apa yang membuat ReentrantReadWriteLockkhusus mencari informasi tentang sinkronisasi utas produsen-konsumen.

Secara umum Anda dapat mengingat bahwa sinkronisasi seluruh metode dan sinkronisasi tujuan umum (menggunakan synchronizedkata kunci) dapat digunakan di sebagian besar aplikasi tanpa terlalu memikirkan semantik sinkronisasi tetapi jika Anda perlu memeras kinerja dari kode Anda, Anda mungkin perlu mengeksplorasi mekanisme sinkronisasi dengan tujuan lebih baik, atau tujuan khusus lainnya.

Ngomong-ngomong, menggunakan synchronized(this)- dan secara umum mengunci menggunakan instance kelas publik - dapat menjadi masalah karena membuka kode Anda ke kunci-mati potensial karena orang lain yang tidak sengaja mungkin mencoba untuk mengunci objek Anda di tempat lain di dalam program.

Mike Dinescu
sumber
untuk mencegah kemungkinan dead-lock karena orang lain yang tidak sengaja mungkin mencoba untuk mengunci objek Anda di tempat lain dalam progame menggunakan instance Object pribadi sebagai sinkronisasi Monitor seperti ini: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta
9

Dari halaman dokumentasi oracle tentang ReentrantLock :

Pengecualian mutual mutual reentrant dengan perilaku dasar dan semantik yang sama dengan kunci monitor implisit yang diakses menggunakan metode dan pernyataan yang disinkronkan, tetapi dengan kemampuan yang diperluas.

  1. Sebuah ReentrantLock dimiliki oleh benang yang terakhir berhasil mengunci, tetapi belum unlocking. Kunci pemanggilan utas akan kembali, berhasil memperoleh kunci, saat kuncinya tidak dimiliki oleh utas lainnya. Metode ini akan segera kembali jika utas saat ini sudah memiliki kunci.

  2. Konstruktor untuk kelas ini menerima parameter keadilan opsional . Ketika disetel benar, di bawah pertentangan, kunci mendukung pemberian akses ke utas terlama . Kalau tidak, kunci ini tidak menjamin urutan akses tertentu.

Fitur kunci ReentrantLock 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.

Kamu bisa memakai ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock untuk mendapatkan kontrol lebih lanjut pada penguncian granular pada operasi baca dan tulis.

Lihat artikel ini oleh Benjamen tentang penggunaan berbagai jenis ReentrantLocks

Ravindra babu
sumber
2

Anda dapat menggunakan kunci reentrant dengan kebijakan keadilan atau batas waktu untuk menghindari kelaparan utas. Anda dapat menerapkan kebijakan kewajaran utas. itu akan membantu menghindari utas menunggu selamanya untuk mendapatkan sumber daya Anda.

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

"Kebijakan keadilan" mengambil utas yang dapat dijalankan berikutnya untuk dijalankan. Ini didasarkan pada prioritas, waktu sejak dijalankan terakhir, bla bla

juga, Sinkronisasi dapat memblokir tanpa batas jika tidak dapat keluar dari blok. Reentrantlock dapat memiliki batas waktu yang ditetapkan.

j2emanue
sumber
1

Kunci yang disinkronkan tidak menawarkan mekanisme antrian menunggu di mana setelah eksekusi satu utas, setiap utas yang berjalan secara paralel dapat memperoleh kunci. Karena itu utas yang ada dalam sistem dan berjalan untuk periode waktu yang lebih lama tidak pernah mendapat kesempatan untuk mengakses sumber daya bersama sehingga mengarah ke kelaparan.

Kunci reentrant sangat fleksibel dan memiliki kebijakan keadilan di mana jika utas menunggu untuk waktu yang lebih lama dan setelah selesainya utas yang saat ini dijalankan, kami dapat memastikan bahwa utas yang lebih lama mendapat kesempatan untuk mengakses sumber daya bersama dengan menurunnya throughput sistem dan membuatnya lebih memakan waktu.

Sumit Kapoor
sumber
1

Mari kita asumsikan kode ini berjalan di utas:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Karena utas memiliki kunci, maka akan memungkinkan beberapa panggilan untuk mengunci (), sehingga memasukkan kembali kunci. Ini dapat dicapai dengan jumlah referensi sehingga tidak harus mendapatkan kunci lagi.

RonTLV
sumber
0

Satu hal yang perlu diingat adalah:

Nama ' ReentrantLock ' memberikan pesan yang salah tentang mekanisme penguncian lain bahwa mereka tidak masuk kembali. Ini tidak benar.Kunci yang diperoleh melalui 'disinkronkan' juga masuk kembali di Jawa.

Perbedaan utama adalah bahwa 'disinkronkan' menggunakan kunci intrinsik (kunci yang dimiliki setiap Objek) sedangkan Kunci API tidak.

Musim panas
sumber
0

Saya pikir metode wait / notify / notifyAll tidak termasuk dalam kelas Object karena mencemari semua objek dengan metode yang jarang digunakan. Mereka jauh lebih masuk akal pada kelas Lock khusus. Jadi dari sudut pandang ini, mungkin lebih baik menggunakan alat yang secara eksplisit dirancang untuk pekerjaan yang ada - yaitu ReentrantLock.

Solubris
sumber