Panduan untuk java.util.concurrent.Locks

1. Ikhtisar

Sederhananya, kunci adalah mekanisme sinkronisasi utas yang lebih fleksibel dan canggih daripada blok tersinkronisasi standar .

The Lock antarmuka telah ada sejak Java 1.5. Ini didefinisikan di dalam paket java.util.concurrent.lock dan menyediakan operasi ekstensif untuk penguncian.

Pada artikel ini, kita akan mempelajari implementasi berbeda dari antarmuka Lock dan aplikasinya.

2. Perbedaan Antara Lock dan Synchronized Block

Ada beberapa perbedaan antara penggunaan blok tersinkronisasi dan penggunaan Lock API:

  • Sebuah disinkronkan blok sepenuhnya terkandung dalam metode - kita dapat memiliki Lock API kunci () dan unlock () operasi dalam metode terpisah
  • Sebuah s blok ynchronized tidak mendukung keadilan, setiap thread dapat memperoleh kunci sekali dirilis, tidak ada preferensi dapat ditentukan. Kami dapat mencapai keadilan dalam Lock API dengan menentukan properti keadilan . Ini memastikan bahwa utas tunggu terlama diberikan akses ke kunci
  • Sebuah utas diblokir jika tidak bisa mendapatkan akses ke blok yang disinkronkan . The Lock API menyediakan tryLock () metode. Utas memperoleh kunci hanya jika tersedia dan tidak dipegang oleh utas lain. Ini mengurangi waktu pemblokiran utas menunggu kunci
  • Sebuah utas yang berada dalam status "menunggu" untuk mendapatkan akses ke blok tersinkronisasi , tidak dapat diganggu. The Lock API menyediakan metode lockInterruptibly () yang dapat digunakan untuk mengganggu benang ketika itu menunggu kunci

3. Kunci API

Mari kita lihat metode di antarmuka Lock :

  • void lock () - dapatkan kunci jika tersedia; jika kunci tidak tersedia, utas akan diblokir hingga kunci dibuka
  • void lockInterruptibly () - ini mirip dengan lock (), tetapi memungkinkan utas yang diblokir untuk diinterupsi dan melanjutkan eksekusi melalui java.lang.InterruptedException yang dilempar
  • boolean tryLock () - ini adalah versi non-pemblokiran darimetode lock () ; ia mencoba untuk mendapatkan kunci dengan segera, mengembalikan true jika penguncian berhasil
  • boolean tryLock (long timeout, TimeUnit timeUnit) - ini mirip dengan tryLock (), kecuali ia menunggu batas waktu yang diberikan sebelum menyerah mencoba untuk mendapatkan Lock
  • void unlock () - membuka kunci instance Lock

Instance yang terkunci harus selalu tidak terkunci untuk menghindari kondisi deadlock. Blok kode yang disarankan untuk menggunakan kunci harus berisi coba / tangkap dan terakhir blok:

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

Selain antarmuka Lock , kami memiliki antarmuka ReadWriteLock yang memelihara sepasang kunci, satu untuk operasi hanya-baca, dan satu untuk operasi tulis. Kunci baca dapat dipegang secara bersamaan oleh beberapa utas selama tidak ada penulisan.

ReadWriteLock mendeklarasikan metode untuk memperoleh kunci baca atau tulis:

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

4. Kunci Implementasi

4.1. ReentrantLock

Kelas ReentrantLock mengimplementasikan antarmuka Lock . Ia menawarkan konkurensi dan semantik memori yang sama, seperti kunci monitor implisit yang diakses menggunakan metode dan pernyataan tersinkronisasi , dengan kapabilitas yang diperluas.

Mari kita lihat, bagaimana kita dapat menggunakan ReenrtantLock untuk sinkronisasi:

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 bahwa kita membungkus panggilan lock () dan unlock () di blok coba-akhirnya untuk menghindari situasi kebuntuan.

Mari kita lihat cara kerja tryLock () :

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

Dalam kasus ini, utas yang memanggil tryLock (), akan menunggu selama satu detik dan akan berhenti menunggu jika kunci tidak tersedia.

4.2. ReentrantReadWriteLock

Kelas ReentrantReadWriteLock mengimplementasikan antarmuka ReadWriteLock .

Mari kita lihat aturan untuk memperoleh ReadLock atau WriteLock dengan utas:

  • Read Lock - jika tidak ada thread yang memperoleh kunci tulis atau memintanya, maka beberapa thread dapat memperoleh kunci baca
  • Write Lock - jika tidak ada thread yang membaca atau menulis maka hanya satu thread yang dapat memperoleh kunci tulis

Mari kita lihat bagaimana menggunakan 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(); } } //... }

For both the write methods, we need to surround the critical section with the write lock, only one thread can get access to it:

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(); } }

For both read methods, we need to surround the critical section with the read lock. Multiple threads can get access to this section if no write operation is in progress.

4.3. StampedLock

StampedLock is introduced in Java 8. It also supports both read and write locks. However, lock acquisition methods return a stamp that is used to release a lock or to check if the lock is still valid:

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); } } }

Another feature provided by StampedLock is optimistic locking. Most of the time read operations don't need to wait for write operation completion and as a result of this, the full-fledged read lock isn't required.

Instead, we can upgrade to read lock:

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

Pada artikel ini, kita telah melihat implementasi berbeda dari antarmuka Lock dan kelas StampedLock yang baru diperkenalkan . Kami juga menjelajahi bagaimana kami dapat menggunakan kelas Kondisi untuk bekerja dengan beberapa kondisi.

Kode lengkap untuk tutorial ini tersedia di GitHub.