Поиск по сайту:

Пример блокировки Java — ReentrantLock


Добро пожаловать в учебник по примерам Java Lock. Обычно при работе с многопоточной средой мы используем синхронизацию для обеспечения безопасности потоков.

Ява-блокировка

  1. Lock: This is the base interface for Lock API. It provides all the features of synchronized keyword with additional ways to create different Conditions for locking, providing timeout for thread to wait for lock. Some of the important methods are lock() to acquire the lock, unlock() to release the lock, tryLock() to wait for lock for a certain period of time, newCondition() to create the Condition etc.

  2. Condition: Condition objects are similar to Object wait-notify model with additional feature to create different sets of wait. A Condition object is always created by Lock object. Some of the important methods are await() that is similar to wait() and signal(), signalAll() that is similar to notify() and notifyAll() methods.

  3. ReadWriteLock: It contains a pair of associated locks, one for read-only operations and another one for writing. The read lock may be held simultaneously by multiple reader threads as long as there are no writer threads. The write lock is exclusive.

  4. ReentrantLock: This is the most widely used implementation class of Lock interface. This class implements the Lock interface in similar way as synchronized keyword. Apart from Lock interface implementation, ReentrantLock contains some utility methods to get the thread holding the lock, threads waiting to acquire the lock etc. synchronized block are reentrant in nature i.e if a thread has lock on the monitor object and if another synchronized block requires to have the lock on the same monitor object then thread can enter that code block. I think this is the reason for the class name to be ReentrantLock. Let’s understand this feature with a simple example.

    public class Test{
    
    public synchronized foo(){
        //do something
        bar();
      }
    
      public synchronized bar(){
        //do some more
      }
    }
    

    If a thread enters foo(), it has the lock on Test object, so when it tries to execute bar() method, the thread is allowed to execute bar() method since it’s already holding the lock on the Test object i.e same as synchronized(this).

Пример блокировки Java — ReentrantLock в Java

Теперь давайте рассмотрим простой пример, в котором мы заменим ключевое слово synchronized на Java Lock API. Допустим, у нас есть класс Resource с некоторой операцией, в которой мы хотим, чтобы она была потокобезопасной, и некоторыми методами, для которых потокобезопасность не требуется.

package com.journaldev.threads.lock;

public class Resource {

	public void doSomething(){
		//do some operation, DB read, write etc
	}
	
	public void doLogging(){
		//logging, no need for thread safety
	}
}

Теперь предположим, что у нас есть класс Runnable, в котором мы будем использовать методы Resource.

package com.journaldev.threads.lock;

public class SynchronizedLockExample implements Runnable{

	private Resource resource;
	
	public SynchronizedLockExample(Resource r){
		this.resource = r;
	}
	
	@Override
	public void run() {
		synchronized (resource) {
			resource.doSomething();
		}
		resource.doLogging();
	}
}

Обратите внимание, что я использую синхронизированный блок для получения блокировки объекта Resource. Мы могли бы создать фиктивный объект в классе и использовать его для блокировки. Теперь давайте посмотрим, как мы можем использовать java Lock API и переписать вышеуказанную программу без использования ключевого слова synchronized. Мы будем использовать ReentrantLock в java.

package com.journaldev.threads.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrencyLockExample implements Runnable{

	private Resource resource;
	private Lock lock;
	
	public ConcurrencyLockExample(Resource r){
		this.resource = r;
		this.lock = new ReentrantLock();
	}
	
	@Override
	public void run() {
		try {
			if(lock.tryLock(10, TimeUnit.SECONDS)){
			resource.doSomething();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			//release lock
			lock.unlock();
		}
		resource.doLogging();
	}

}

Как видите, я использую метод tryLock(), чтобы убедиться, что мой поток ожидает только определенное время, и если он не получает блокировку объекта, он просто регистрируется и завершается. Другим важным моментом, который следует отметить, является использование блока try-finally, чтобы убедиться, что блокировка снята, даже если вызов метода doSomething() выдает какое-либо исключение.

Java Lock против синхронизированного

Основываясь на приведенных выше деталях и программе, мы можем легко сделать вывод о следующих различиях между Java Lock и синхронизацией.

  1. Java Lock API обеспечивает большую видимость и возможности для блокировки, в отличие от синхронизированного, когда поток может бесконечно ждать блокировки, мы можем использовать tryLock(), чтобы убедиться, что поток ожидает только определенное время.
  2. Код синхронизации намного чище и прост в сопровождении, в то время как в случае с Lock мы вынуждены иметь блок try-finally, чтобы убедиться, что Lock будет выпущен, даже если между вызовами методов lock() и unlock() возникнет какое-то исключение.
  3. блоки или методы синхронизации могут охватывать только один метод, тогда как мы можем получить блокировку в одном методе и снять ее в другом методе с помощью Lock API.
  4. Ключевое слово synchronized не обеспечивает равноправия, тогда как мы можем установить равноправие равным true при создании объекта ReentrantLock, чтобы самый длинный ожидающий поток получал блокировку первым.
  5. Мы можем создавать разные условия для блокировки, и разные потоки могут использовать await() для разных условий.

Это все, что касается примера блокировки Java, ReentrantLock в java и сравнительного анализа с ключевым словом synchronized.