Что такое семафор и мьютекс в Java — Java Concurrency MultiThread объясняется на примере
Опубликовано: 2015-03-12Java Concurrency — очень широкая тема. Существуют сотни учебных пособий и примеров, доступных для использования. Некоторое время назад я написал несколько руководств по одновременному запуску нескольких потоков в Java и различным типам синхронизированных блоков.
В этом уроке мы рассмотрим:
- Объяснение мьютекса
- Объяснение семафора
- Два примера с деталями
Давайте начнем
Let's keep this in mind
этом, читая объяснение ниже:
- Берите пример с Покупателя и Клиента
- Покупатель одалживает ноутбуки
- Клиент может прийти и использовать ноутбук — клиенту нужен ключ для использования ноутбука
- После использования — клиент может вернуть ноутбук покупателю.
Что такое Mutex (всего 1 поток):
У покупателя есть ключ к ноутбуку. Ключ может быть у одного клиента — одолжить ноутбук — в то время. Когда задача завершается, Покупатель передает (освобождает) ключ следующему покупателю в очереди.
Official Definition
:
«Мьютекс обычно используется для сериализации доступа к разделу re-entrant code
, который cannot be executed concurrently
более чем одним потоком. Объект мьютекса позволяет только одному потоку войти в контролируемый раздел, заставляя другие потоки, пытающиеся получить доступ к этому разделу, ждать, пока первый поток не выйдет из этого раздела».
Другими словами: Mutex = Mutually Exclusive Semaphore
.
Что такое семафор (N указанных потоков):
Допустим, теперь у Shopper есть 3 одинаковых ноутбука и 3 одинаковых ключа. Семафор – это количество free identical Laptop keys
. Счетчик семафоров – количество ключей – в начале устанавливается равным 3 (все три ноутбука свободны), затем значение счетчика уменьшается по мере поступления клиента. Если все ноутбуки используются, т.е. не осталось свободных ключей для Ноутбук, количество семафоров равно 0. Теперь, когда любой из клиентов возвращает ноутбук, семафор увеличивается до 1 (один свободный ключ) и передается следующему покупателю в очереди.
Official Definition
: «Семафор ограничивает количество одновременных пользователей общего ресурса до максимального числа. Потоки могут запрашивать доступ к ресурсу (уменьшая значение семафора) и сигнализировать об окончании использования ресурса (увеличивая значение семафора)».
Еще нужно прочитать: Ленивое создание экземпляра Singleton ThreadSafe
Пример-1: (Пояснение ниже)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
package crunchify . com . tutorial ; import java . util . LinkedList ; import java . util . concurrent . Semaphore ; /** * @author Crunchify.com * */ public class CrunchifySemaphoreMutexTutorial { static Object crunchifyLock = new Object ( ) ; static LinkedList <String> crunchifyList = new LinkedList <String> ( ) ; // Semaphore maintains a set of permits. // Each acquire blocks if necessary until a permit is available, and then takes it. // Each release adds a permit, potentially releasing a blocking acquirer. static Semaphore semaphore = new Semaphore ( 0 ) ; static Semaphore mutex = new Semaphore ( 1 ) ; // I'll producing new Integer every time static class CrunchifyProducer extends Thread { public void run ( ) { int counter = 1 ; try { while ( true ) { String threadName = Thread . currentThread ( ) . getName ( ) + counter ++ ; mutex . acquire ( ) ; crunchifyList . add ( threadName ) ; System . out . println ( "Producer is prdoucing new value: " + threadName ) ; mutex . release ( ) ; // release lock semaphore . release ( ) ; Thread . sleep ( 500 ) ; } } catch ( Exception x ) { x . printStackTrace ( ) ; } } } // I'll be consuming Integer every stime static class CrunchifyConsumer extends Thread { String consumerName ; public CrunchifyConsumer ( String name ) { this . consumerName = name ; } public void run ( ) { try { while ( true ) { // acquire lock. Acquires the given number of permits from this semaphore, blocking until all are // available // process stops here until producer releases the lock semaphore . acquire ( ) ; // Acquires a permit from this semaphore, blocking until one is available mutex . acquire ( ) ; String result = "" ; for ( String value : crunchifyList ) { result = value + "," ; } System . out . println ( consumerName + " consumes value: " + result + "crunchifyList.size(): " + crunchifyList . size ( ) + "\n" ) ; mutex . release ( ) ; } } catch ( Exception e ) { e . printStackTrace ( ) ; } } } public static void main ( String [ ] args ) { new CrunchifyProducer ( ) . start ( ) ; new CrunchifyConsumer ( "Crunchify" ) . start ( ) ; new CrunchifyConsumer ( "Google" ) . start ( ) ; new CrunchifyConsumer ( "Yahoo" ) . start ( ) ; } } |
В приведенном выше руководстве CrunchifySemaphoreMutexTutorial.java
когда CrunchifyProducer
добавляет threadName
к crunchifyList
crunchifyList, он может сигнализировать семафору.
Затем CrunchifyConsumer
может попытаться получить семафор, поэтому он будет ждать, пока CrunchifyProducer не сообщит о добавлении идентификатора потока. После подачи сигнала о добавленных данных один из потребителей будет разбужен и узнает, что может прочитать объект crunchifyList. Он может прочитать список, а затем вернуться к попытке получить данные на семафоре.
Если за это время производитель записал еще один пакет, он снова подаст сигнал, и любой из потребителей продолжит чтение другого пакета и так далее…
Другими словами:
1 2 3 |
CrunchifyProducer : Add an object o List - Semaphore . release ( 1 ) CrunchifyConsumer x N ) - Semaphore . acquire ( 1 ) - Read an object from List |

Результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Producer is prdoucing new value : Thread - 01 Crunchify consumes value : Thread - 01 , crunchifyList . size ( ) : 1 Producer is prdoucing new value : Thread - 02 Google consumes value : Thread - 02 , crunchifyList . size ( ) : 2 Producer is prdoucing new value : Thread - 03 Yahoo consumes value : Thread - 03 , crunchifyList . size ( ) : 3 Producer is prdoucing new value : Thread - 04 Crunchify consumes value : Thread - 04 , crunchifyList . size ( ) : 4 . . . . . . . . . . . . . . . |
Как предотвратить состояние гонки:
What if you have multiple Consumers?
В приведенном выше учебнике по Java потребители (не производитель) должны блокировать буфер при чтении пакета (но не при получении семафора), чтобы предотвратить условия гонки. В приведенном ниже примере производитель также блокирует список, поскольку все находится на одной и той же JVM.
Пример-2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
package crunchify . com . tutorial ; import java . util . concurrent . Semaphore ; /** * @author Crunchify.com * */ public class CrunchifyJavaSemaphoreTutorial { private static final int MAX_CONCURRENT_THREADS = 2 ; private final Semaphore crunchifyAdminLOCK = new Semaphore ( MAX_CONCURRENT_THREADS , true ) ; public void crunchifyStartTest ( ) { for ( int i = 0 ; i < 10 ; i ++ ) { CrunchifyPerson person = new CrunchifyPerson ( ) ; person . start ( ) ; } } public class CrunchifyPerson extends Thread { @Override public void run ( ) { try { // Acquire Lock crunchifyAdminLOCK . acquire ( ) ; } catch ( InterruptedException e ) { System . out . println ( "received InterruptedException" ) ; return ; } System . out . println ( "Thread " + this . getId ( ) + " start using Crunchify's car - Acquire()" ) ; try { sleep ( 1000 ) ; } catch ( Exception e ) { } finally { // Release Lock crunchifyAdminLOCK . release ( ) ; } System . out . println ( "Thread " + this . getId ( ) + " stops using Crunchify's car - Release()\n" ) ; } } public static void main ( String [ ] args ) { CrunchifyJavaSemaphoreTutorial test = new CrunchifyJavaSemaphoreTutorial ( ) ; test . crunchifyStartTest ( ) ; } } |
Результат:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
Thread 11 start using Crunchify 's car - Acquire() Thread 10 start using Crunchify' s car - Acquire ( ) Thread 10 stops using Crunchify 's car - Release() Thread 12 start using Crunchify' s car - Acquire ( ) Thread 13 start using Crunchify 's car - Acquire() Thread 11 stops using Crunchify' s car - Release ( ) Thread 13 stops using Crunchify 's car - Release() Thread 15 start using Crunchify' s car - Acquire ( ) Thread 14 start using Crunchify 's car - Acquire() Thread 12 stops using Crunchify' s car - Release ( ) Thread 14 stops using Crunchify 's car - Release() Thread 16 start using Crunchify' s car - Acquire ( ) Thread 15 stops using Crunchify 's car - Release() Thread 17 start using Crunchify' s car - Acquire ( ) Thread 17 stops using Crunchify 's car - Release() Thread 18 start using Crunchify' s car - Acquire ( ) Thread 19 start using Crunchify 's car - Acquire() Thread 16 stops using Crunchify' s car - Release ( ) Thread 18 stops using Crunchify 's car - Release() Thread 19 stops using Crunchify' s car - Release ( ) |