Elettracompany.com

Компьютерный справочник
0 просмотров
Рейтинг статьи
1 звезда2 звезды3 звезды4 звезды5 звезд
Загрузка...

Java util concurrent locks

Блокировки пакета concurrent

Обычная синхронизация обеспечивает надежное обновление множественных общих переменных в многопоточном приложении предупреждая гонку или повреждение данных и гарантирует, что параллельные потоки, синхронизируемые должным образом, увидят последние значения этих переменных. Но такая синхронизация не совершена и имеет некоторые функциональные ограничения :

  • невозможно прервать поток, который ожидает блокировки;
  • невозможно опрашивать или пытаться получить блокировку, не будучи готовым к долгому ожиданию;
  • блокировка должна быть снята в том же стековом фрейме, в котором была начата.

Пакет java.util.concurrent.locks включает классы, которые можно использовать для блокировки ресурсов с определенными условиями, и которые существенно отличаются от встроенной синхронизации и мониторов. Этот пакет разрешает намного большую гибкость в использовании блокировок без условий и с условием. Классы пакета реализуют следующие интерфейсы :

На странице наряду с описанием блокировок представлены три примера их использования :

  • пример использования блокировки ReentrantLock;
  • пример блокировки методом lockInterruptibly;
  • пример использования условия блокировки Condition.

Интерфейс Lock

Интерфейс Lock — это абстракция, допускающая выполнение блокировок, которые реализуются как классы Java, а не как возможность языка (объекта). Это расширяет возможности применения Lock, которые могут иметь различные алгоритмы планирования. Блокировка Lock является инструментом для того, чтобы управлять доступом к совместно используемому ресурсу паралельными потоками.

Реализации интерфейса Lock существенно расширяют возможности блокировок по сравнению c synchronized. Интерфейс Lock позволяет осуществлять более гибкое структурирование и поддерживает многократно связанный условный объект Condition.

Методы интерфейса Lock

Дополнительные возможности, предоставляемые блокировкой Lock накладывают определенные обязанности при ее использовании. Так, отсутствие блочно-структурированной блокировки исключает автоматическое ее освобождение, как это происходит с synchronized. Поэтому следует использовать следующую структуру кода, включающую блокировку Lock :

Подробное описание интерфейса Lock представлено здесь.

Класс ReentrantLock

Класс ReentrantLock, реализующий интерфейс Lock, также, как и synchronized, обеспечивает многопоточность, но имеет дополнительные возможности, связанные с опросом о блокировании (lock polling), ожиданием блокирования в течение определенного времени и прерыванием ожидания блокировки. Кроме того, ReentrantLock предлагает гораздо более высокую эффективность функционирования в условиях жесткой состязательности. Другими словами, когда несколько потоков пытаются получить доступ к совместно используемому ресурсу, виртуальной машине JVM потребуется меньше времени на установление очередности потоков и больше времени на ее выполнение.

В переводе reentrant может означать повторно используемый (повторный вход). Что может означать блокировка с повторным входом? Это учет количества получения определенных блокировок. Т.е. один и тот же поток повторно получает одну и ту же блокировку. Но для того, чтобы реально разблокировать необходимо уже будет два раза снять блокировку. Это аналогично использованию synchronized; если поток повторно входит в синхронный блок, защищенный монитором, то блокировка не будет снята при выходе потока из второго (или последующего) блока synchronized, блокировка будет снята только когда поток выйдет из первого блока synchronized, в который он вошел под защитой монитора.

Одним из интересных методов интерфейса Lock и его реализации ReentrantLock является запрос блокировки с возможностью прерывания процесса ожидания. Т.е. если поток запрашивает блокировку методом lockInterruptibly() и не получает ее сразу же, то переходит в процесс ожидания. Методом interrupt работу потока можно прервать. Тогда ожидающий блокировки поток просыпается, и генерируется исключительная ситуация InterruptedException. После этого попыток доступа к защищенному ресурсу (получения блокировок) не делается и освобождать блокировку не требуется. Ниже представлен пример использования блокировки lockInterruptibly. Структура кода использования блокировки lockInterruptibly имеет следующий вид :

Внутренний блок try-finally получает блокировку и доступ к защищенным ресурсам; после завершения работы блокировка освобождается. Внешний блок try-catch обрабатывает исключительные ситуации запроса блокировки. Если поток прерван в результате исключительной ситуации, то выполняется перехват catch (InterruptedException) и метод снятия блокировки unlock не вызывается.

Практика показывает, что реализация ReentrantLock гораздо более масштабируемая в условиях состязательности, чем synchronized. Это значит, что если несколько потоков соперничают за право получения блокировки, общая пропускная способность обычно лучше у ReentrantLock, чем у synchronized. И наоборот, если особого столкновения за право получения блокировки не наблюдается, то можно использовать и synchronized.

Подробное описание ReentrantLock представлено здесь.

Пример использования ReentrantLock

В примере ReentrantLockExample, листинг которого представлен ниже, используется внутренний класс LockClass для организации двух потоков. Константы TIME_WAIT и TIME_SLEEP используются потоками для организации определенных задержек при выполнении. Текстовая переменная resource используется в качестве общего ресурса, значение которого будет изменяться внутри потоков. Метод printMessage выводит в консоль сообщения потоков с указанием времени.

В конструкторе примера создается блокировка lock типа ReentrantLock и два потока, которые будут использовать lock для блокирования доступа к текстовому ресурсу. Сначала каждый поток пытается в течение определенного времени (TIME_WAIT, мс) блокировать доступ к ресурсу resource с использованием метода tryLock. Если блокировка получена, то текст строки resource изменяется. После этого в потоке выполняется некоторая задержка по времени (TIME_SLEEP, мс) и поток завершает свою работу с освобождением блокировки методом unlock. Если поток в течение времени TIME_WAIT не смог блокировать ресурс, то он переходит к стадии задержки и завершению работы.

Оперируя временем ожидания блокировки TIME_WAIT и временем задержки TIME_SLEEP можно дать возможность либо каждому из потоку изменить значение resource, либо только одному.

Сообщения потоков

Пример исполнен для двух вариантов значений констант TIME_WAIT и TIME_SLEEP. В первом варианте значение TIME_WAIT, равное 7c, больше значения TIME_SLEEP (5c). Поэтому оба потока получают доступ к ресурсу и изменяют значение ресурса.

Во втором варианте время ожидания TIME_WAIT (5c), меньше времени задержки TIME_SLEEP (7c). Поэтому только один поток получает доступ к ресурсу для изменения значения ресурса; второй поток разрешение на блокировку не получил по времени.

Пример блокировки lockInterruptibly

Основу примера использования lockInterruptibly составляет предыдущий пример ReentrantLockExample. Поэтому ниже в листингах будут представлены только изменения в коде. Полную версию примера можно скачать в конце страницы.

Основная идея примера LockInterruptiblyExample связана с тем, чтобы в очередь (в ожидание) на получение блокировки поставить два потока, а в первом потоке, получившем блокировку, прервать работу одного из потоков. Все потоки используют измененный, по сравнению с исходным примером, класс LockClass, реализующий интерфейс Runnable.

Следующий код демонстрирует старт трёх потоков; второй и третий потоки стартуют с небольшой задержкой, чтобы надежно первый поток захватил блокировку lock, в противном случае первым захватить блокировку может и второй поток, работу которого необходимо прерывать.

Метод run класса LockClass (листинг ниже) претерпевает значительные изменения. В первую очередь это касается вызов метода lockInterruptibly для получения блокировки. После этого следует небольшая задержка в 2 сек, и далее выполняется проверка, если это первый поток, то он прерывает работу второго потока. После чего первый поток дает возможность второму потоку завершить работу, а сам изменяет и печатает строку общего ресурса, снимает блокировку и завершает работу. Второй же поток не попадает в секцию try. finally, а завершается с исключением, которое перехватывает catch (InterruptedException).

Сообщения потоков

Ниже представлены сообщения потоков. Сначала потоки запрашивают блокировку, первый поток, пришедший первым, получает ее, а два потока остаются в ожидании освобождения/получения блокировки. После небольшой задержки первый поток прерывает работу второго потока и ждет его завершения. Далее первый поток освобождает блокировку и завершает работу. Сразу же после этого третий поток получает блокировку и далее по сценарию.

Интерфейс Condition

Интерфейсное условие Condition в сочетании с блокировкой Lock позволяет заменить методы монитора/мьютекса (wait, notify и notifyAll) объектом, управляющим ожиданием событий. Блокировка Lock заменяет использование synchronized, а Condition — объектные методы монитора.

Многопоточное программирование в Java 8. Часть вторая. Синхронизация доступа к изменяемым объектам

    Переводы, 21 сентября 2016 в 18:56

Добро пожаловать во вторую часть руководства по параллельному программированию в Java 8. В предыдущей части мы рассматривали, как выполнять код параллельно с помощью потоков, задач и сервисов исполнителей. Сегодня мы разберём, как синхронизировать доступ к изменяемым объектам с помощью ключевого слова synchronized , блокировок и семафоров.

Большинство принципов, которые описаны в этой статье, справедливы и для более старых версий Java. Тем не менее, я не задавался проблемами совместимости, и примеры содержат как лямбды, так и новые возможности многопоточности. Если вы не очень хорошо знакомы с лямбда-выражениями, то вам стоит сперва прочитать мой туториал по Java 8.

Читать еще:  Разработка игр на языке javascript скачать

Для простоты примеров я использую в них два метода-помощника: sleep(секунды) и stop(сервис-исполнитель) . Их реализации я выложил на GitHub, если кому-то интересно.

Синхронизация

Мы уже узнали, как выполнять код параллельно с помощью сервисов-исполнителя (ExecutorService). Во время написания многопоточной программы нужно уделять особое внимание работе с общими для потоков изменяемыми объектами. Давайте представим, что мы хотим увеличить такую переменную на единицу.

Мы создаём поле count и метод increment() , который увеличивает count на единицу:

Если мы будем вызывать этот метод одновременно из двух потоков, у нас возникнут серьёзные проблемы:

Вместо ожидаемого постоянного результата 10000 мы будем каждый раз получать разные числа. Причина этого — использование изменяемой переменной несколькими потоками без синхронизации, что вызывает состояние гонки (race condition).

LATOKEN, Москва, от 3500 до 5000 $

Увеличение числа на единицу происходит в три шага: (1) считать значение переменной, (2) увеличить это значение на единицу и (3) записать назад новое значение. Если два потока будут одновременно выполнять эти шаги, то вполне вероятно, что они могут выполнить первый шаг одновременно, считав одно и то же значение. Затем они запишут в переменную одно и то же значение, и вместо увеличения на 2 получится увеличение на единицу. Поэтому конечное значение и получается меньше ожидаемого.

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

Тогда, после выполнения метода 10000 раз, мы всегда будем получать значение 10000, и никакой гонки состояний возникать не будет:

Это ключевое слово можно применять не только к методам, но и к отдельным их блокам:

Под капотом Java использует так называемый монитор (monitor lock, intrinsic lock) для обеспечения синхронизации. Этот монитор привязан к объекту, поэтому синхронизированные методы используют один и тот же монитор соответствующего объекта. Все неявные мониторы устроены реентерабельно (reentrant), т.е. таким образом, что поток может без проблем вызывать блокировку одного и того же объекта, исключая взаимную блокировку (например, когда синхронизированный метод вызывает другой синхронизированный метод на том же объекте).

Блокировки

Кроме использования блокировок неявно (с помощью ключевого слова synchronized ), Concrurrency API предлагает много способов их явного использования, определённых интерфейсом Lock . С помощью явных блокировок можно настроить работу программы гораздо тоньше и тем самым сделать её эффективнее.

Стандартный JDK предоставляет множество реализаций Lock , которые мы сейчас и рассмотрим.

ReentrantLock

Класс ReentrantLock реализует то же поведение, что и обычные неявные блокировки. Давайте попробуем переписать наш пример с увеличением на единицу с помощью него:

Блокировка осуществляется с помощью метода lock() , а освобождаются ресурсы помощью метода unlock() . Очень важно оборачивать код в try<>finally<> , чтобы ресурсы освободились даже в случае выброса исключения. Код, представленный выше, так же потокобезопасен, как и его аналог с synchronized . Если один поток вызвал lock() , и другой поток пытается получить доступ к методу до вызова unlock() , то второй поток будет простаивать до тех пор, пока метод не освободится. Только один поток может удерживать блокировку в каждый момент времени.

Для большего контроля явные блокировки поддерживают множество специальных методов:

Пока первый поток удерживает блокировку, второй выведет следующую информацию:

Locked: true
Held by me: false
Lock acquired: false

Метод tryLock() , в отличие от обычного lock() не останавливает текущий поток в случае, если ресурс уже занят. Он возвращает булевый результат, который стоит проверить перед тем, как пытаться производить какие-то действия с общими объектами (истина обозначает, что контроль над ресурсами захватить удалось).

ReadWriteLock

Интерфейс ReadWriteLock предлагает другой тип блокировок — отдельную для чтения, и отдельную для записи. Этот интерфейс был добавлен из соображения, что считывать данные (любому количеству потоков) безопасно до тех пор, пока ни один из них не изменяет переменную. Таки образом, блокировку для чтения (read-lock) может удерживать любое количество потоков до тех пор, пока не удерживает блокировка для записи (write-lock). Такой подход может увеличить производительность в случае, когда чтение используется гораздо чаще, чем запись.

В примере выше мы можем видеть, как поток блокирует ресурсы для записи, после чего ждёт одну секунду, записывает данные в HashMap и освобождает ресурсы. Предположим, что в это же время были созданы ещё два потока, которые хотят получить из хэш-таблицы значение:

Если вы попробуете запустить этот пример, то заметите, что оба потока, созданные для чтения, будут простаивать секунду, ожидая завершения работы потока для записи. После снятия блокировки они выполнятся параллельно, и одновременно запишут результат в консоль. Им не нужно ждать завершения работы друг друга, потому что выполнять одновременное чтение вполне безопасно (до тех пор, пока ни один поток не работает параллельно на запись).

StampedLock

В Java 8 появился новый тип блокировок — StampedLock . Так же, как и в предыдущих примерах, он поддерживает разделение на readLock() и writeLock() . Однако, в отличие от ReadWriteLock , метод блокировки StampedLock возвращает “штамп” — значение типа long . Этот штамп может использоваться в дальнейшем как для высвобождения ресурсов, так и для проверки состояния блокировки. Вдобавок, у этого класса есть методы, для реализации “оптимистичной” блокировки. Но обо всём по порядку.

Вот таким образом следовало бы переписать наш предыдущий пример под использование StampedLock :

Работать этот код будет точно так же, как и его брат-близнец с ReadWriteLock . Тут, правда, стоит упомянуть, что в StampedLock не реализована реентерантность. Поэтому особое внимание нужно уделять тому, чтобы не попасть в ситуацию взаимной блокировки (deadlock).

В следующем примере демонстрируется “оптимистичная блокировка”:

Оптимистичная блокировка для чтения, вызываемая с помощью метода tryOptimisticRead() , отличается тем, что она всегда будет возвращать “штамп” не блокируя текущий поток, вне зависимости от того, занят ли ресурс, к которому она обратилась. В случае, если ресурс был заблокирован блокировкой для записи, возвращённый штамп будет равняться нулю. В любой момент можно проверить, является ли блокировка валидной с помощью lock.validate(stamp) . Для приведённого выше кода результат будет таким:

Optimistic Lock Valid: true
Write Lock acquired
Optimistic Lock Valid: false
Write done
Optimistic Lock Valid: false

Оптимистичная блокировка является валидной с того момента, как ей удалось захватить ресурс. В отличии от обычных блокировок для чтения, оптимистичная не запрещает другим потокам блокировать ресурс для записи. Что же происходит в коде выше? После захвата ресурса блокировка является валидной и оптимистичный поток отправляется спать. В это время другой поток блокирует ресурсы для записи, не дожидаясь окончания работы чтения. Начиная с этого момента, оптимистичная блокировка перестаёт быть валидной (даже после окончания записи).

Таким образом, при использовании оптимистичных блокировок вам нужно постоянно следить за их валидностью (проверять её нужно уже после того, как выполнены все необходимые операции).

Иногда может быть полезным преобразовать блокировку для чтения в блокировку для записи не высвобождая ресурсы. В StampedLock это можно сделать с помощью метода tryConvertToWriteLock() , как в этом примере:

В этом примере мы хотим прочитать значение переменной count и вывести его в консоль. Однако, если значение равно нулю, мы хотим изменить его на 23. Для этого нужно выполнить преобразования из readLock во writeLock, чтобы не помешать другим потокам обрабатывать переменную. В случае, если вы вызвали tryConvertToWriteLock() в тот момент, когда ресурс занят для записи другим потоком, текущий поток остановлен не будет, однако метод вернёт нулевое значение. В таком случае можно вызвать writeLock() вручную.

Семафоры

Семафоры — отличный способ ограничить количество потоков, которые одновременно работают над одним и тем же ресурсом:

В этом примере сервис-исполнитель может потенциально запустить все 10 вызываемых потоков, однако мы создали семафор, который ограничивает количество одновременно выполняемых потоков до пяти. Снова напомню, что важно освобождать ресурсы именно в блоке finally<> на случай выброса исключений. Для приведённого выше кода вывод будет следующим:

Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Semaphore acquired
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore
Could not acquire semaphore

Читать еще:  Как восстановить аккаунт гугл если он удален

Это была вторая часть серии статей про многопоточное программирование. Настоятельно рекомендую разобрать вышеприведенные примеры самостоятельно. Все они, как обычно, доступны на GitHub. Можете смело форкать репозиторий и добавлять его в избранное.

Надеюсь, вам понравилась статья. Если у вас возникли какие-либо вопросы, вы можете задать их в твиттере.

How to Use Locks in Java | java.util.concurrent.locks.Lock Tutorial and Example

By Lokesh Gupta | Filed Under: Java Concurrency

We are already aware of basic concepts around thread synchronization and various mechanisms using synchronized keyword. Java provides another mechanism for the synchronization of blocks of code based on the Lock interface and classes that implement it (such as ReentrantLock ). In this tutorial, we will see a basic usage of Lock interface to solve printer queue problem.

Lock Interface

A java.util.concurrent.locks.Lock is a thread synchronization mechanism just like synchronized blocks. A Lock is, however, more flexible and more sophisticated than a synchronized block. Since Lock is an interface, you need to use one of its implementations to use a Lock in your applications. ReentrantLock is one such implementation of Lock interface.

Here is the simple use of Lock interface.

First a Lock is created. Then it’s lock() method is called. Now the Lock instance is locked. Any other thread calling lock() will be blocked until the thread that locked the lock calls unlock() . Finally unlock() is called, and the Lock is now unlocked so other threads can lock it.

Difference between Lock Interface and synchronized keyword

The main differences between a Lock and a synchronized block are:

1) Having a timeout trying to get access to a synchronized block is not possible. Using Lock.tryLock(long timeout, TimeUnit timeUnit), it is possible.
2) The synchronized block must be fully contained within a single method. A Lock can have it’s calls to lock() and unlock() in separate methods.

Simulating Printer Queue using Locks

In this example, program will simulate the behavior of a printer. You can submit a number of print jobs to printer during varying time interval or simultaneously. Printer will take a job from printer queue and print it. Rest of jobs will wait there for their turn. Once printer is done with print job in hand, it will pick another job from queue and start printing. Keep this happening in a loop.

PrintingJob.java

This class represents an independent printing which could be submitted to printer. This class implements Runnable interface, so that printer can execute it when it’s turn come.

PrinterQueue.java

This class represent the printer queue/ printer. A lock is maintained by printer to start new print job as soon as current print job is finished.

Let’s test our printer program:

The key to the example is in the printJob() method of the PrinterQueue class. When we want to implement a critical section using locks and guarantee that only one execution thread runs a block of code, we have to create a ReentrantLock object. At the beginning of the critical section, we have to get the control of the lock using the lock() method.

At the end of the critical section, we have to use the unlock() method to free the control of the lock and allow the other threads to run this critical section. If you don’t call the unlock() method at the end of the critical section, the other threads that are waiting for that block will be waiting forever, causing a deadlock situation. If you use try-catch blocks in your critical section, don’t forget to put the sentence containing the unlock() method inside the finally section.

Java util concurrent locks

Java Concurrency Lock and Condition Examples

Java concurrency framework provides external Locks which are similar to intrinsic lock obtained entering synchronized blocks but give flexibility and provide other features. In this tutorial, you can learn Lock and ReadWriteLock interfaces, ReentrantLock and ReentrantReadWriteLock lock implementations and Conditions with examples.

Table of Contents

Locks

When a thread enters synchronized block of statements or synchronized methods, it obtains the lock of interested object in which there is a shared data between threads to avoid data corruption. Locks allow only one thread at time to access the share resources. But the intrinsic lock obtained this way has limited operations. It is not flexible as it can only be acquired when it enters a block and must be released when it exist the block. If a thread acquires multiple locks, they must be releases in the opposite order meaning the last acquired lock is the first one to be released, then second latest one, and so on.

Java concurrency framework Locks provide extensive locking operations and allow flexibility. Lock implementations provided in concurrency framework allow acquiring and releasing locks at any scope, meaning for releasing locks, order doesn’t have to be followed like intrinsic lock. This feature is called hand over hand or chain locking, with the feature, a thread can acquire lock A and lock B, then release lock A, then acquired lock C and so on.

With this external locks, a thread can try to acquire a lock for certain duration of time then back out if it can’t acquire lock in the specified time duration or a thread can immediately back out if a lock is not available. And also, when a thread is waiting for or trying to acquire lock, if the thread is interrupted, the operation will not block the thread.

Like intrinsic lock, Lock object can be owned by one thread at a time and inter thread communication can be implemented using wait and notify mechanism. Condition object provide means for a thread to wait for a condition to change and be notified by other threads.

Lock Interface

Lock interface in Java concurrency framework defines methods for acquiring locks in different forms. Method lock() is similar to intrinsic lock, can block the current thread if lock can’t be acquired and can cause deadlock.

Method lockInterruptibly() allows the current thread to acquire the lock if it is available and the thread is not interrupted. If lock is not available and thread is not interrupted, it will wait till the lock is available.

Method tryLock() allows the current uninterrupted thread to acquire the lock if available. Otherwise, it will exit or retry till the specified time expires depending on the version of tryLock() method.

Method unlock() releases the lock. It is a best practice to keep the block of statements, which execute after acquiring the lock, in try block and call unlock method on the lock object in the finally block to release the lock.

ReentrantLock Example

ReentrantLock implements Lock interface and it is similar to intrinsic lock. Current thread obtains lock by calling lock method on it. If lock is not available, thread will block waiting for lock. When multiple threads wait for lock, ReentrantLock can be made to give priority using fair policy mechanism to the longest waiting thread by giving lock it. ReentrantLock with fairness policy can be created by passing true value to its constructor.

In addition to implementing methods defined in the Lock interface, ReentrantLock provides methods such as getHoldCount(), getOwner(), getQueuedThreads(), isFiar(), isHeldByCurrentThread() and newConditions().

Following example shows how to use ReentrantLock. The example instantiates ReentrantLock object and calls lock method on it in both threads. First thread obtains lock and executes task while second thread waits for the lock to be released. Second thread obtains the lock after first thread is done with execution. Method unlock is called in the finally block to release the lock. To check whether lock is obtained by the current thread, you can use isHeldByCurrentThread() method.

Читать еще:  Отбросить дробную часть javascript

If you call tryLock() method to obtain lock in both threads, second thread will not block and continue execution. Here is the output of the above program with tryLock() instead of lock()

You can make a thread to try to lock for certain amount of time.

If you add above code to second thread in our example, you will get output similar to first use case. The only difference is that if second thread is interrupted by calling interrupt() method on it, tryLock will throw interrupted exception and will not block the thread waiting the lock.

You can get a lock using lockInterruptibly() method also, the difference between lock and lockInterruptibly methods is that lockInterruptibly method doesn’t block or provide lock if the current thread is interrupted.

ReadWriteLock

Threads can obtain read lock and write lock using ReadWriteLock. Read lock is for read only operations and write lock is for writes. Thread which acquires read lock can see updates made by the thread which previously released the write lock.

Though ReadWriteLock has read and write lock, only one lock, either read lock or write is used at a given point of time. But multiple threads can own read locks at the same time as long as there are no writers. ReadWriteLock improves the performance where there are a lot of reads and few writes as multiple threads can simultaneously own read lock.

ReadWriteLock interface defines two methods for threads to obtain read and write locks, readLock() and writeLock() respectively. ReentrantReadWriteLock is an implementation of ReadWriteLock interface.

ReentrantReadWriteLock Example

Following example shows how to use read and write locks using ReentrantReadWriteLock.

ReentrantReadWriteLock can be used in fair mode by passing true value to its constructor. When used in fair mode, ReentrantReadWriteLock assigns write lock to the longest waiting thread. If group of threads waiting for read lock, then it will assign the read lock to the group. When used in non-fair mode, order of granting locks is not maintained.

ReentrantReadWriteLock supports reentrancy meaning a reader which acquired read lock can reacquire read lock. Similarly a writer which acquired write lock can reacquire write lock. And also, a writer which acquired write lock can acquire read lock, but reverse is not allowed meaning write to read downgrade is allowed and read to write lock upgrade is not possible. Reentrancy is useful in the scenario where a writer calls methods which use read lock.

Condition

Like threads communicate using wait(), notify() and notifyAll() methods of object when intrinsic lock is used to restrict access to a shared objects, Condition allows inter threads communication when external locks are used. Condition defines mechanism to suspend a thread until another thread notifies it. Using condition, deadlock issue can be avoided with external locks.

Condition object is obtained by calling newCondition() methods on lock object, ReentrantLock or ReentrantReadWriteLock. Condition defines methods such as await(), signal() and signalAll() for waiting and notifying. Overloaded await() methods allows you to specify the duration of the wait. Method siganlAll() notifies all waiting threads.

Condition Example

Following example shows how to use condition object for communication between two threads when ReentrantLock is used to restrict access to share message object which contains methods to publish and view messages. If the last message is not consumed, publish-message thread waits using a condition object and calling awaits() on it. Similarly, if there is no new message, view-message thread waits using another condition object and calling awaits method on it.

When publish-message thread sets new message, it will notify view-message thread by calling signal() on the condition object on which view-message thread is waiting. Similarly, when view-message thread consumes the message, it will notify publish-message thread by calling signal() method on the condition object on which publish-message thread is waiting.

Start two threads.

About

Android app development tutorials and web app development tutorials with programming examples and code samples.

java.util.concurrent.locks.Condition Example

In this tutorial we will discuss about the Condition interface in Java. A Condition object, also known as condition variable , provides a thread with the ability to suspend its execution, until the condition is true. A Condition object is necessarily bound to a Lock and can be obtained using the newCondition() method.

Furthermore, a Condition enables the effect of having multiple wait-sets per object, by combining these sets with the use of a Lock implementation. Moreover, due to the fact that Conditions access portions of state shared among different threads, the usage of a Lock is mandatory. It is important to mention that a Condition must atomically release the associated Lock and suspend the current’s thread execution.

Finally, the Condition interface exists since the 1.5 version of Java.

The structure of the Condition interface

Methods

The current thread suspends its execution until it is signalled or interrupted.

The current thread suspends its execution until it is signalled, interrupted, or the specified amount of time elapses.

The current thread suspends its execution until it is signalled, interrupted, or the specified amount of time elapses.

The current thread suspends its execution until it is signalled (cannot be interrupted).

The current thread suspends its execution until it is signalled, interrupted, or the specified deadline elapses.

This method wakes a thread waiting on this condition.

This method wakes all threads waiting on this condition.

Conditions in Java

As we have already described, Conditions are being used in order for a thread to be notified, when a condition is true. One fundamental example that demonstrates the usage of Conditions is the producer-consumer example. According to this model, a thread produces a number of items and places them to a shared queue, while a thread consumes these objects, by removing them from the shared queue.

Important: Notice that the model supports the presence of multiple producers and consumers, but in this example, we will demonstrate the simple case where we have one producer and one consumer.

In addition, it is important to mention that the shared queue is accessed by multiple threads and thus, it must be properly synchronized. Our implementation of the shared queue is shown below:

The SharedQueue class contains a private array of elements and a maximum capacity. It supports two methods, add and remove , which are used to add and remove an element to the queue respectively. In both methods, the lock is first acquired. Then, if the queue is not full, an element can be inserted, or correspondingly, if the queue is not empty, an element can be removed. Finally, before the lock is released, both methods notify any waiting thread.

The Producer class reads the contents of the specified file, line-by-line. Each line is split into separated words and each word is placed into the shared queue. Once the file has been completely read, a special null object is placed into the queue, in order to notify the consumer that no more elements will be placed into the queue.

The Consumer class constantly reads elements from the shared queue, until a special null object is received. The Consumer class also counts the number of distinct words, as received by the producer.

In a sample main method, we create one instance of each class and wait for both threads to terminate. A sample execution is shown below:

Download the Eclipse Project

This was a tutorial about the Condition interface in Java.

Ссылка на основную публикацию
Adblock
detector