Panduan untuk java.util.concurrent.Locks

1. Gambaran keseluruhan

Ringkasnya, kunci adalah mekanisme penyegerakan benang yang lebih fleksibel dan canggih daripada blok penyegerakan standard .

Antara muka Lock telah wujud sejak Java 1.5. Ia ditentukan dalam pakej java.util.concurrent.lock dan menyediakan operasi yang luas untuk mengunci.

Dalam artikel ini, kita akan meneroka pelbagai pelaksanaan antara muka Lock dan aplikasinya.

2. Perbezaan Antara Kunci dan Penyegerakan Blok

Terdapat beberapa perbezaan antara penggunaan blok yang disegerakkan dan menggunakan Lock API's:

  • A disegerakkan blok terkandung sepenuhnya dalam kaedah yang - kita boleh mempunyai Lock API kunci () dan buka kunci () operasi dalam kaedah berasingan
  • A s blok ynchronized tidak menyokong keadilan, mana-mana thread boleh memperoleh kunci sekali dilepaskan, tidak ada keutamaan boleh dinyatakan. Kami dapat mencapai keadilan dalam API Kunci dengan menentukan harta yang adil . Ini memastikan bahawa benang menunggu terpanjang diberi akses ke kunci
  • Benang tersekat jika tidak mendapat akses ke blok yang disegerakkan . The Lock API menyediakan tryLock () kaedah. Benang memperoleh kunci hanya jika tersedia dan tidak dipegang oleh utas lain. Ini mengurangkan masa menyekat benang menunggu kunci
  • Benang yang dalam keadaan "menunggu" untuk memperoleh akses ke blok yang disegerakkan , tidak dapat diganggu. The Lock API menyediakan kaedah yang lockInterruptibly () yang boleh digunakan untuk mengganggu thread apabila ia menunggu kunci

3. Kunci API

Mari lihat kaedah di antara muka Kunci :

  • kekunci tidak sah () - dapatkan kunci jika ada; jika kunci tidak tersedia, utas akan tersekat sehingga kunci dilepaskan
  • kekosongan batal Tidak terganggu () - ini serupa dengan kunci (), tetapi ia membolehkan benang yang disekat terganggu dan meneruskan pelaksanaan melalui java.lang yang dilemparkan.
  • boolean tryLock () - ini adalahkaedah kunci () kaedahtidak menyekat; ia berusaha mendapatkan kunci dengan segera, kembali benar jika penguncian berjaya
  • boolean tryLock (timeout panjang, TimeUnit timeUnit) - ini serupa dengan tryLock (), kecuali menunggu masa yang ditentukan sebelum berhenti berusaha untuk mendapatkan Lock
  • tidak sah buka kunci () - mengungkapkan Lock contoh

Contoh terkunci harus selalu dibuka untuk mengelakkan keadaan kebuntuan. Blok kod yang disyorkan untuk menggunakan kunci harus mengandungi cubaan / tangkapan dan akhirnya menyekat:

Lock lock = ...; lock.lock(); try { // access to the shared resource } finally { lock.unlock(); }

Sebagai tambahan kepada antara muka Kunci , kami mempunyai antara muka ReadWriteLock yang mengekalkan sepasang kunci, satu untuk operasi baca sahaja, dan satu untuk operasi tulis. Kunci baca boleh dipegang secara serentak oleh beberapa utas selagi tidak ada tulisan.

ReadWriteLock menyatakan kaedah untuk memperoleh kunci baca atau tulis:

  • Lock readLock () - mengembalikan kunci yang digunakan untuk membaca
  • Lock writeLock () - mengembalikan kunci yang digunakan untuk menulis

4. Pelaksanaan Kunci

4.1. ReentrantLock

Kelas ReentrantLock menerapkan antara muka Lock . Ia menawarkan semantik bersamaan dan memori yang sama, seperti kunci monitor tersirat yang diakses menggunakan kaedah dan pernyataan yang diselaraskan , dengan kemampuan yang diperluas.

Mari lihat, bagaimana kita boleh menggunakan ReenrtantLock untuk penyegerakan:

public class SharedObject { //... ReentrantLock lock = new ReentrantLock(); int counter = 0; public void perform() { lock.lock(); try { // Critical section here count++; } finally { lock.unlock(); } } //... }

Kita perlu memastikan bahawa kita membungkus panggilan kunci () dan buka kunci () di blok percubaan akhirnya untuk mengelakkan situasi kebuntuan.

Mari lihat bagaimana tryLock () berfungsi:

public void performTryLock(){ //... boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS); if(isLockAcquired) { try { //Critical section here } finally { lock.unlock(); } } //... } 

Dalam kes ini, thread memanggil tryLock (), akan menunggu satu saat dan akan berhenti menunggu sekiranya kunci tidak tersedia.

4.2. ReentrantReadWriteLock

Kelas ReentrantReadWriteLock melaksanakan antara muka ReadWriteLock .

Mari lihat peraturan untuk memperoleh ReadLock atau WriteLock melalui utas :

  • Baca Kunci - jika tidak ada benang yang memperoleh kunci tulis atau memintanya, banyak utas dapat memperoleh kunci baca
  • Tulis Kunci - jika tiada utas membaca atau menulis maka hanya satu utas yang dapat memperoleh kunci tulis

Mari lihat bagaimana memanfaatkan ReadWriteLock :

public class SynchronizedHashMapWithReadWriteLock { Map syncHashMap = new HashMap(); ReadWriteLock lock = new ReentrantReadWriteLock(); // ... Lock writeLock = lock.writeLock(); public void put(String key, String value) { try { writeLock.lock(); syncHashMap.put(key, value); } finally { writeLock.unlock(); } } ... public String remove(String key){ try { writeLock.lock(); return syncHashMap.remove(key); } finally { writeLock.unlock(); } } //... }

Untuk kedua-dua kaedah menulis, kita perlu mengelilingi bahagian kritikal dengan kunci tulis, hanya satu utas yang dapat mengaksesnya:

Lock readLock = lock.readLock(); //... public String get(String key){ try { readLock.lock(); return syncHashMap.get(key); } finally { readLock.unlock(); } } public boolean containsKey(String key) { try { readLock.lock(); return syncHashMap.containsKey(key); } finally { readLock.unlock(); } }

Untuk kedua-dua kaedah membaca, kita perlu mengelilingi bahagian kritikal dengan kunci baca. Beberapa utas boleh mendapatkan akses ke bahagian ini jika tiada operasi tulis sedang dijalankan.

4.3. StampedLock

StampedLock diperkenalkan di Java 8. Ia juga menyokong kunci baca dan tulis. Walau bagaimanapun, kaedah pemerolehan kunci mengembalikan setem yang digunakan untuk melepaskan kunci atau untuk memeriksa sama ada kunci masih sah:

public class StampedLockDemo { Map map = new HashMap(); private StampedLock lock = new StampedLock(); public void put(String key, String value){ long stamp = lock.writeLock(); try { map.put(key, value); } finally { lock.unlockWrite(stamp); } } public String get(String key) throws InterruptedException { long stamp = lock.readLock(); try { return map.get(key); } finally { lock.unlockRead(stamp); } } }

Ciri lain yang disediakan oleh StampedLock adalah mengunci optimis. Sebilangan besar operasi membaca tidak perlu menunggu operasi tulis dan akibatnya, kunci baca lengkap tidak diperlukan.

Sebagai gantinya, kita boleh meningkatkan ke kunci baca:

public String readWithOptimisticLock(String key) { long stamp = lock.tryOptimisticRead(); String value = map.get(key); if(!lock.validate(stamp)) { stamp = lock.readLock(); try { return map.get(key); } finally { lock.unlock(stamp); } } return value; }

5. Working With Conditions

The Condition class provides the ability for a thread to wait for some condition to occur while executing the critical section.

This can occur when a thread acquires the access to the critical section but doesn't have the necessary condition to perform its operation. For example, a reader thread can get access to the lock of a shared queue, which still doesn't have any data to consume.

Traditionally Java provides wait(), notify() and notifyAll() methods for thread intercommunication. Conditions have similar mechanisms, but in addition, we can specify multiple conditions:

public class ReentrantLockWithCondition { Stack stack = new Stack(); int CAPACITY = 5; ReentrantLock lock = new ReentrantLock(); Condition stackEmptyCondition = lock.newCondition(); Condition stackFullCondition = lock.newCondition(); public void pushToStack(String item){ try { lock.lock(); while(stack.size() == CAPACITY) { stackFullCondition.await(); } stack.push(item); stackEmptyCondition.signalAll(); } finally { lock.unlock(); } } public String popFromStack() { try { lock.lock(); while(stack.size() == 0) { stackEmptyCondition.await(); } return stack.pop(); } finally { stackFullCondition.signalAll(); lock.unlock(); } } }

6. Conclusion

Dalam artikel ini, kita telah melihat pelaksanaan yang berbeza dari antara muka Lock dan kelas StampedLock yang baru diperkenalkan . Kami juga meneroka bagaimana kami dapat memanfaatkan kelas Condition untuk bekerja dengan pelbagai keadaan.

Kod lengkap untuk tutorial ini boleh didapati di GitHub.