Класс Semaphore, примеры реализации кода в Java

Класс Semaphore, примеры реализации кода в JavaПервым сразу же распознаваемым среди объектов синхронизации является семафор, реализуемый в классе Semaphore.

Семафор управляет доступом к обще­му ресурсу с помощью счетчика. Если счетчик больше нуля, доступ разрешается,а если он равен нулю, то в доступе будет отказано.

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

Как правило, поток исполнения, которому требуется доступ к общему ресурсу, и пытается получить разрешение, чтобы воспользоваться семафором. Если значе­ние счетчика семафора окажется больше нуля, поток исполнения получит разре­шение, после чего значение счетчика семафора уменьшается на единицу. В про­тивном случае поток будет заблокирован до тех пор, пока он не сумеет получить разрешение.

Если потоку исполнения доступ к общему ресурсу больше не нужен,он освобождает разрешение, в результате чего значение счетчика семафора увеличивается на единицу. Если в это время другой поток исполнения ожидает разрешения, то он сразу же его получает. В Java этот механизм реализуется в классе Semaphore.

В классе Semaphore имеются два приведенных ниже конструктора:

Здесь параметр число обозначает исходное значение счетчика разрешений. Таким образом, параметр число определяет количество потоков исполнения, ко­торым может быть одновременно предоставлен доступ к общему ресурсу.

Если па­раметр число принимает значение 1, к ресурсу может обратиться только один по­ток исполнения. По умолчанию ожидающим потокам исполнения предоставляет­ся разрешение в неопределенном порядке.

Если же присвоить параметру способ логическое значение true, то тем самым можно гарантировать, что разрешения будут предоставляться ожидающим потокам исполнения в том порядке, в каком они запрашивали доступ.

Чтобы получить разрешение, достаточно вызвать метод acquire(), который имеет две формы:

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

Если разрешение не будет предоставлено вовремя вызова метода, то исполнение вызывающего потока будет приостановлено до тех пор, пока не будет получено разрешение.

Чтобы освободить разрешение, следует вызвать метод release(). Ниже при­ведены общие формы этого метода:

В первой форме освобождается одно разрешение, а во второй — количество разрешений, обозначаемое параметром число.

Чтобы воспользоваться семафором для управления доступом к ресурсу, каж­дый поток исполнения, которому требуется этот ресурс, должен вызвать метод acquire(), прежде чем обращаться к ресурсу.

Когда поток исполнения завершает пользование ресурсом, он должен вызвать метод release(), чтобы освободить ресурс. В приведенном ниже примере программы демонстрируется применение семафора.

Ниже приведен примерный результат выполнения данной программы. (Конкретный порядок следования потоков исполнения может быть иным.)

Для управления доступом к переменной count, которая является статической переменной класса Shared, в данной программе используется семафор. Значение переменной Shared.count увеличивается на 5 в методе run() из класса IncThread и уменьшается на 5 в одноименном методе из класса DecThread.

Для защиты по­токов исполнения, представленных этими двумя классами, от одновременного доступа к переменной Shared.count такой доступ предоставляется только после того, как будет получено разрешение от управляющего семафора.

По завершении доступа к данной переменной как к общему ресурсу разрешение на него освобож­дается. Таким образом, только один поток исполнения может одновременно полу­чить доступ к переменной Shared.count, что и подтверждают результаты выпол­нения данной программы.

Обратите внимание на то, что в методе run() из классов IncThread и DecThread вызывается метод sleep(). Он гарантирует, что доступ к переменной Shared.count будет синхронизироваться семафором.

В частности, вызов метода sleep() из метода run() приводит к тому, что вызывающий поток исполнения будет при­останавливаться в промежутках между последовательными попытками доступа к переменной Shared.count. Это, как правило, позволяет исполняться второму потоку.

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

Таким образом, значение переменной Shared.count сначала увеличивается на 5 в объекте класса IncThread, а затем уменьшается на 5 в объ­екте класса DecThread. Увеличение и уменьшение значения этой переменной про­исходит строго по порядку.

Если бы в данном примере не использовался семафор, то попытки доступа к переменной Shared.count, производимые каждым потоком исполнения, осу­ществлялись бы одновременно, поэтому увеличение и уменьшение значения этой переменной происходило бы не по порядку.

Чтобы убедиться в этом, попробуй­те закомментировать вызовы методов acquire() и release(). Запустив данную программу на выполнение, вы обнаружите, что доступ к переменной Shared.count больше не является синхронизированным, и каждый поток исполнения обращается к переменной Shared.count, как только для него выделяется времен­ной интервал.

Класс Semaphore, примеры реализации кода в JavaНесмотря на то что применение семафора, как правило, не представляет осо­бой сложности, как демонстрируется в предыдущем примере программы, возмож­ны и более сложные варианты его применения.

Ниже приведен один из таких примеров. Он представляет собой переработанную версию программы, реализую­щей функции поставщика и потребителя . В данном варианте использу­ются два семафора, регулирующие потоки исполнения поставщика и потребителя и гарантирующие, что после каждого вызова метода put() будет следовать соот­ветствующий вызов метода get().

Ниже показана результат, выводимый данной программой:

Как видите, в данном примере синхронизируются вызовы методов put() и get(). Это означает, что после каждого вызова метода put() следует вызов метода get() и поэтому ни одно значение не может быть пропущено.

Если бы не семафо­ры, вызовы метода put() могли бы происходить несогласованно с вызовами метода get(), что привело бы к пропуску некоторых значений. (Чтобы убедиться в этом,удалите код семафора из данного примера и посмотрите полученные результаты.)

Надлежащая последовательность вызовов методов put() и get() соблюдается двумя семафорами: semProd и semCon.

Прежде чем метод put() сможет предоставить значение, он должен получить разрешение от семафора semProd. Установив значение, он освобождает семафор semProd.

Прежде чем метод get() сможет употребить значение, он должен получить разрешение от семафора semCon.Употребив значение, он освобождает семафор semCon. Такой механизм передачи и получения значений гарантирует, что после каждого вызова метода put() будет следовать вызов метода get().

Обратите внимание на то, что семафор semCon инициализируется без доступ­ных разрешений. Этим гарантируется, что метод put() выполняется первым. Возможность задавать исходное состояние синхронизации является одной из са­мых сильных сторон семафоров.