¿Qué es Java Semaphore y Mutex? Java Concurrency MultiThread explicado con un ejemplo
Publicado: 2015-03-12Java Concurrency es un tema muy amplio. Hay cientos de tutoriales y ejemplos disponibles para su uso. Hace algún tiempo, escribí algunos tutoriales sobre Ejecutar múltiples subprocesos simultáneamente en Java y diferentes tipos de bloques sincronizados.
En este tutorial repasaremos:
- Explicación de Mutex
- Explicación del semáforo
- Dos ejemplos con detalles
Empecemos
Let's keep this in mind
mientras leemos la siguiente explicación:
- Tome un ejemplo de Shopper y Customer
- El comprador está tomando prestadas las computadoras portátiles
- El cliente puede venir y usar la computadora portátil: el cliente necesita una clave para usar una computadora portátil
- Después de su uso, el cliente puede devolver la computadora portátil al comprador
Qué es Mutex (Solo 1 hilo):
El comprador tiene una llave de una computadora portátil. Un cliente puede tener la llave, tomar prestada una computadora portátil, en ese momento. Cuando finaliza la tarea, el Comprador entrega (libera) la llave al siguiente cliente en la cola.
Official Definition
:
“Mutex generalmente se usa para serializar el acceso a una sección de re-entrant code
que cannot be executed concurrently
por más de un hilo. Un objeto mutex solo permite que un subproceso ingrese a una sección controlada, lo que obliga a otros subprocesos que intentan obtener acceso a esa sección a esperar hasta que el primer subproceso haya salido de esa sección.
En otras palabras: Mutex = Mutually Exclusive Semaphore
Qué es Semaphore (N subprocesos especificados):
Digamos que ahora Shopper tiene 3 Laptops idénticas y 3 llaves idénticas. El semáforo es el número de free identical Laptop keys
. El conteo de semáforos, el conteo de llaves, se establece en 3 al principio (las tres computadoras portátiles están libres), luego el valor de conteo disminuye a medida que ingresa el Cliente. Si todas las computadoras portátiles están en uso, es decir, no quedan llaves libres para Laptop, el conteo de semáforos es 0. Ahora, cuando cualquiera de los clientes devuelve la Laptop, el semáforo aumenta a 1 (una clave libre) y se entrega al siguiente cliente en la cola.
Official Definition
: “Un semáforo restringe el número de usuarios simultáneos de un recurso compartido hasta un número máximo. Los subprocesos pueden solicitar acceso al recurso (reduciendo el semáforo) y pueden indicar que han terminado de usar el recurso (incrementando el semáforo)”.
Otra lectura obligada: Lazy Creation of Singleton ThreadSafe Instance
Ejemplo-1: (Explicación a continuación)
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 ( ) ; } } |
En el tutorial anterior CrunchifySemaphoreMutexTutorial.java
cuando CrunchifyProducer
agrega threadName
al objeto crunchifyList
linkedList, puede señalar el semáforo.
El CrunchifyConsumer
puede estar tratando de adquirir el semáforo, por lo que esperará hasta que CrunchifyProducer haya indicado que se ha agregado un threadID. Al señalar un dato agregado, uno de los consumidores se despertará y sabrá que puede leer un objeto crunchifyList. Puede leer una lista y luego volver a intentar adquirir en el semáforo.
Si en ese tiempo el productor ha escrito otro paquete, ha señalado nuevamente y cualquiera de los consumidores continuará leyendo otro paquete y así sucesivamente...
En otras palabras:
1 2 3 |
CrunchifyProducer : Add an object o List - Semaphore . release ( 1 ) CrunchifyConsumer x N ) - Semaphore . acquire ( 1 ) - Read an object from List |

Resultado:
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 . . . . . . . . . . . . . . . |
Cómo prevenir la condición de carrera:
What if you have multiple Consumers?
En el tutorial de Java anterior, los consumidores (no el productor) deben bloquear el búfer al leer el paquete (pero no al adquirir el semáforo) para evitar condiciones de carrera. En el siguiente ejemplo, el productor también bloquea la lista ya que todo está en la misma JVM.
Ejemplo-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 ( ) ; } } |
Resultado:
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 ( ) |