Лучше обрабатывать синхронизацию, чем ключевое слово synchronized.

Мы уже узнали, как синхронизировать общий ресурс с помощью ключевого слова synchronized. В этой статье мы увидим лучший способ синхронизации общих ресурсов с помощью ReentrantLock.

Для синхронизации с использованием ключевого слова synchronized вы можете обратиться к этой статье: Синхронизация в Java: все, что вам нужно знать

Проблемы с синхронизированным ключевым словом:

  • Поток может получить блокировку объекта только один раз для общих ресурсов.
  • Как только поток завершает выполнение синхронизированного блока, не существует механизма, с помощью которого другие потоки получат блокировку.
  • Давайте рассмотрим сценарий и поймем проблему starvation с несколькими потоками с ключевым словом synchronized. Допустим, есть 10 потоков (от t1 до t10) и потоков (t1-t9), которые получают блокировку и снимают блокировку после выполнения синхронизированного блока.
  • Но может быть сценарий, когда поток t10 не получает возможности выполнить синхронизированный блок, поскольку другие потоки конкурируют за блокировку. Следовательно, это может привести к истощению, и t10 должен очень долго ждать выполнения синхронизированного блока.

Решение: возвратный замок:

Java предоставляет лучший способ справиться с вышеуказанными проблемами, связанными с синхронизацией.

  • ReentrantLock class реализует интерфейс Lock. Этот класс предоставляется в java. util.concurrent.
import java.util.concurrent.locks.ReentrantLock;
  • Мы должны создать объект класса ReentrantLock, и общий ресурс должен быть окружен методами lock и unlock этого объекта.
  • Поток, который хочет выполнить общий ресурс, должен принять эту блокировку, и, следовательно, все другие потоки, пытающиеся заблокировать этот объект, должны дождаться, пока текущий поток не освободит его.
  • Теперь прелесть ReentrantLock в том, что он позволяет текущему потоку брать блокировку на один и тот же объект блокировки несколько раз. Обратите внимание, что когда текущий поток сначала вызывает метод lock (), счетчик удержаний устанавливается равным единице. Этот поток может снова вызвать метод lock () и каждый раз, когда счетчик удержаний увеличивается на единицу.
  • Для каждого запроса на разблокировку счетчик удержаний уменьшается на единицу, а когда счетчик удержаний равен 0, ресурс разблокируется.
  • Конструктор этого класса принимает необязательный параметр fairness. Если установлено значение true, в условиях конкуренции блокировки предпочитают предоставление доступа к потоку, ожидающему дольше всего. В противном случае эта блокировка не гарантирует какой-либо конкретный порядок доступа.

Теперь давайте посмотрим, как мы можем использовать этот механизм:

public void methodName(){
    bufferLock.lock();
    try {
            //sharedResource
    } catch(Exception e) {
            e.printStackTrace();
    } finally {
            bufferLock.unlock();
    }
}

В приведенном выше фрагменте кода вы можете заметить, что поток должен принять блокировку перед выполнением общего ресурса. И после выполнения блока try поток снимет блокировку в блоке finally, используя вызов метода unlock (). Обратите внимание, что независимо от того, возникнет ли исключение, finally block будет выполняться всегда.

Я уже подробно освещал проблему производителя-потребителя в этой статье.

Я бы посоветовал вам сначала прочитать эту статью. После этого следуйте примеру ниже. В основном вместо synchronized я использовал ReentrantLock.



Класс продюсера:

Класс производителя для создания элементов в буфере, чтобы Потребитель мог потреблять элементы из этого буфера.

Потребительский класс:

Потребительский класс для потребления предметов из буфера, созданного Производителем.

Класс водителя:

Примечание. Метод Unlock () всегда вызывается в блоке finally, чтобы гарантировать снятие блокировки даже в случае возникновения исключения в блоке try.

Важные методы для класса ReentrantLock:

  • int getHoldCount(): Запрашивает количество удержаний этой блокировки текущим потоком. (Сколько раз метод lock () вызывается текущим потоком).
  • boolean isHeldByCurrentThread(): запрашивает, удерживается ли эта блокировка текущим потоком.
  • protected Thread getOwner(): возвращает поток, которому в данный момент принадлежит эта блокировка, или null, если он не принадлежит.
  • protected Collection<Thread> getQueuedThreads(): возвращает коллекцию, содержащую потоки, которые могут ожидать получения этой блокировки.
  • boolean hasQueuedThread(Thread thread): запрашивает, ожидает ли данный поток получения этой блокировки.
  • boolean hasQueuedThreads(): запрашивает, ожидают ли какие-либо потоки получения этой блокировки.
  • boolean isFair(): возвращает true, если для этой блокировки установлено значение справедливости.
  • boolean isLocked(): запрашивает, удерживается ли эта блокировка каким-либо потоком.
  • void lock(): получает блокировку.
  • boolean tryLock(): получает блокировку, только если она не удерживается другим потоком во время вызова.
  • void unlock(): пытается снять эту блокировку.

Итак, это все для этой статьи. Думаю, вы получили хорошее представление о реентерабельных блокировках. Надеюсь, вам понравилась эта статья 😃.

Если вам понравилась эта статья, подумайте о следите за мной. В основном я пишу о Java-программировании, структурах данных, алгоритмах, SQL, GIT, Linux, лучших практиках и т. Д.

Возможно, вас заинтересуют следующие статьи о многопоточности Java: