Лучше обрабатывать синхронизацию, чем ключевое слово 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: