Semafori u Javi
1. Pregled
U ovom brzom vodiču istražit ćemo osnove semafora i muteksa u Javi.
2. Semafor
Počet ćemo s java.util.concurrent.Semaphore. Možemo koristiti semafore da ograničimo broj istodobnih niti koje pristupaju određenom resursu.
U sljedećem ćemo primjeru implementirati jednostavan red prijave kako bismo ograničili broj korisnika u sustavu:
klasa LoginQueueUsingSemaphore {privatni semafor Semaphore; javni LoginQueueUsingSemaphore (int slotLimit) {semaphore = novi Semaphore (slotLimit); } logička probaLogin () {povratak semafor.tryAcquire (); } void logout () {semaphore.release (); } int availableSlots () {vratiti semafor.availablePermissions (); }}
Primijetite kako smo koristili sljedeće metode:
- tryAcquire () - vrati true ako je dozvola odmah dostupna i pribavi je inače vrati false, ali steći() stekne dozvolu i blokira dok je ne postane dostupna
- release () - izdati dozvolu
- availablePermissions () - povratni broj dostupnih trenutnih dozvola
Da bismo testirali svoj red za prijavu, prvo ćemo pokušati doseći ograničenje i provjeriti hoće li sljedeći pokušaj prijave biti blokiran:
@Test javna praznina givenLoginQueue_whenReachLimit_thenBlocked () {int slots = 10; ExecutorService executorService = Izvršitelji.newFixedThreadPool (utora); LoginQueueUsingSemaphore loginQueue = novi LoginQueueUsingSemaphore (mjesta); IntStream.range (0, slots) .forEach (user -> executorService.execute (loginQueue :: tryLogin)); executorService.shutdown (); assertEquals (0, loginQueue.availableSlots ()); assertFalse (loginQueue.tryLogin ()); }
Dalje, vidjet ćemo jesu li dostupni utori nakon odjave:
@Test javna praznina givenLoginQueue_whenLogout_thenSlotsAvailable () {int slots = 10; ExecutorService executorService = Izvršitelji.newFixedThreadPool (utora); LoginQueueUsingSemaphore loginQueue = novi LoginQueueUsingSemaphore (mjesta); IntStream.range (0, slots) .forEach (user -> executorService.execute (loginQueue :: tryLogin)); executorService.shutdown (); assertEquals (0, loginQueue.availableSlots ()); loginQueue.logout (); assertTrue (loginQueue.availableSlots ()> 0); assertTrue (loginQueue.tryLogin ()); }
3. Vrijeme Semafor
Dalje ćemo razgovarati o Apache Commons Tempirani semafor. Tempirani semafor dopušta brojne dozvole kao jednostavan Semafor, ali u određenom vremenskom razdoblju, nakon tog razdoblja resetiranje vremena i sve dozvole se oslobađaju.
Možemo koristiti Tempirani semafor za izgradnju jednostavnog reda odgode na sljedeći način:
klasa DelayQueueUsingTimedSemaphore {privatni semafor TimedSemaphore; DelayQueueUsingTimedSemaphore (dugo razdoblje, int slotLimit) {semafor = novi TimedSemaphore (period, TimeUnit.SECONDS, slotLimit); } logička probaAdd () {return semaphore.tryAcquire (); } int availableSlots () {return semaphore.getAvailablePermissions (); }}
Kada koristimo red odgode s jednom sekundom kao vremenskim razdobljem i nakon upotrebe svih mjesta unutar jedne sekunde, nijedan ne bi trebao biti dostupan:
javna praznina givenDelayQueue_whenReachLimit_thenBlocked () {int slots = 50; ExecutorService executorService = Izvršitelji.newFixedThreadPool (utora); DelayQueueUsingTimedSemaphore delayQueue = novi DelayQueueUsingTimedSemaphore (1, mjesta); IntStream.range (0, slots) .forEach (user -> executorService.execute (delayQueue :: tryAdd)); executorService.shutdown (); assertEquals (0, delayQueue.availableSlots ()); assertFalse (delayQueue.tryAdd ()); }
Ali nakon spavanja neko vrijeme, semafor bi trebao resetirati i pustiti dozvole:
@Test javna praznina givenDelayQueue_whenTimePass_thenSlotsAvailable () baca InterruptedException {int slots = 50; ExecutorService executorService = Izvršitelji.newFixedThreadPool (utora); DelayQueueUsingTimedSemaphore delayQueue = novi DelayQueueUsingTimedSemaphore (1, mjesta); IntStream.range (0, slots) .forEach (korisnik -> executorService.execute (delayQueue :: tryAdd)); executorService.shutdown (); assertEquals (0, delayQueue.availableSlots ()); Navoj.spavanje (1000); assertTrue (delayQueue.availableSlots ()> 0); assertTrue (delayQueue.tryAdd ()); }
4. Semafor protiv muteksa
Mutex djeluje slično binarnom semaforu, možemo ga koristiti za uzajamno isključivanje.
U sljedećem ćemo primjeru koristiti jednostavni binarni semafor za izgradnju brojača:
klasa CounterUsingMutex {privatni mutex Semaphore; broj privatnih int; CounterUsingMutex () {mutex = novi Semafor (1); broj = 0; } void povećanje () baca InterruptedException {mutex.acquire (); this.count = this.count + 1; Navoj.spavanje (1000); mutex.release (); } int getCount () {return this.count; } boolean hasQueuedThreads () {return mutex.hasQueuedThreads (); }}
Kada puno niti pokuša odjednom pristupiti brojaču, jednostavno će biti blokirani u redu čekanja:
@Test public void whenMutexAndMultipleThreads_thenBlocked () baca InterruptedException {int count = 5; ExecutorService executorService = Izvršitelji.newFixedThreadPool (count); CounterUsingMutex counter = novi CounterUsingMutex (); IntStream.range (0, count) .forEach (user -> executorService.execute (() -> {try {counter.increase ();} catch (InterruptedException e) {e.printStackTrace ();}}))); executorService.shutdown (); assertTrue (counter.hasQueuedThreads ()); }
Kad čekamo, sve će niti pristupiti brojaču, a niti jedna nit neće ostati u redu:
@Test javna praznina givenMutexAndMultipleThreads_ThenDelay_thenCorrectCount () baca InterruptedException {int count = 5; ExecutorService executorService = Izvršitelji.newFixedThreadPool (count); CounterUsingMutex counter = novi CounterUsingMutex (); IntStream.range (0, count) .forEach (user -> executorService.execute (() -> {try {counter.increase ();} catch (InterruptedException e) {e.printStackTrace ();}}))); executorService.shutdown (); assertTrue (counter.hasQueuedThreads ()); Navoj.spavanje (5000); assertFalse (counter.hasQueuedThreads ()); assertEquals (count, counter.getCount ()); }
5. Zaključak
U ovom smo članku istražili osnove semafora u Javi.
Kao i uvijek, puni izvorni kod dostupan je na GitHub-u.