Semaforer i Java
1. Oversigt
I denne hurtige vejledning udforsker vi det grundlæggende i semaforer og mutexer i Java.
2. Semafor
Vi starter med java.util.concurrent.Semaphore. Vi kan bruge semaforer til at begrænse antallet af samtidige tråde, der får adgang til en bestemt ressource.
I det følgende eksempel implementerer vi en simpel login-kø for at begrænse antallet af brugere i systemet:
klasse LoginQueueUsingSemaphore {private Semaphore semaphore; offentlig LoginQueueUsingSemaphore (int slotLimit) {semaphore = ny Semaphore (slotLimit); } boolsk tryLogin () {return semaphore.tryAcquire (); } ugyldig logout () {semaphore.release (); } int availableSlots () {return semaphore.availablePermits (); }}
Bemærk hvordan vi brugte følgende metoder:
- tryAcquire () - returnere sandt, hvis en tilladelse er tilgængelig med det samme og erhverve den ellers returnere falsk, men erhverve() erhverver en tilladelse og spærring, indtil en er tilgængelig
- frigør () - frigiv en tilladelse
- tilgængeligePermits () - returnere antallet af nuværende tilgængelige tilladelser
For at teste vores loginkø, vil vi først prøve at nå grænsen og kontrollere, om det næste loginforsøg blokeres:
@Test offentlig ugyldighed givenLoginQueue_whenReachLimit_thenBlocked () {int slots = 10; ExecutorService executorService = Executors.newFixedThreadPool (slots); LoginQueueUsingSemaphore loginQueue = ny LoginQueueUsingSemaphore (slots); IntStream.range (0, slots) .forEach (user -> executorService.execute (loginQueue :: tryLogin)); executorService.shutdown (); assertEquals (0, loginQueue.availableSlots ()); assertFalse (loginQueue.tryLogin ()); }
Dernæst vil vi se, om der er ledige pladser efter en logout:
@Test offentlig ugyldighed givenLoginQueue_whenLogout_thenSlotsAvailable () {int slots = 10; ExecutorService executorService = Executors.newFixedThreadPool (slots); LoginQueueUsingSemaphore loginQueue = ny LoginQueueUsingSemaphore (slots); 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. Tidsindstillet Semafor
Dernæst vil vi diskutere Apache Commons TimedSemaphore. TimedSemaphore tillader et antal tilladelser som et simpelt semafor, men i en given tidsperiode, efter denne periode nulstilles tiden og alle tilladelser frigives.
Vi kan bruge TimedSemaphore at opbygge en simpel forsinkelseskø som følger:
klasse DelayQueueUsingTimedSemaphore {private TimedSemaphore semaphore; DelayQueueUsingTimedSemaphore (lang periode, int slotLimit) {semaphore = ny TimedSemaphore (periode, TimeUnit.SECONDS, slotLimit); } boolsk tryAdd () {return semaphore.tryAcquire (); } int availableSlots () {return semaphore.getAvailablePermits (); }}
Når vi bruger en forsinkelseskø med et sekund som tidsperiode, og efter at have brugt alle slots inden for et sekund, skal ingen være tilgængelige:
offentlig ugyldighed givenDelayQueue_whenReachLimit_thenBlocked () {int slots = 50; ExecutorService executorService = Executors.newFixedThreadPool (slots); DelayQueueUsingTimedSemaphore delayQueue = ny DelayQueueUsingTimedSemaphore (1, slots); IntStream.range (0, slots) .forEach (user -> executorService.execute (delayQueue :: tryAdd)); executorService.shutdown (); assertEquals (0, delayQueue.availableSlots ()); assertFalse (delayQueue.tryAdd ()); }
Men efter at have sovet i nogen tid, semaforen skal nulstille og frigive tilladelserne:
@Test offentlig ugyldighed givenDelayQueue_whenTimePass_thenSlotsAvailable () kaster InterruptedException {int slots = 50; ExecutorService executorService = Executors.newFixedThreadPool (slots); DelayQueueUsingTimedSemaphore delayQueue = ny DelayQueueUsingTimedSemaphore (1, slots); IntStream.range (0, slots) .forEach (user -> executorService.execute (delayQueue :: tryAdd)); executorService.shutdown (); assertEquals (0, delayQueue.availableSlots ()); Tråd. Søvn (1000); assertTrue (delayQueue.availableSlots ()> 0); assertTrue (delayQueue.tryAdd ()); }
4. Semaphore vs. Mutex
Mutex fungerer på samme måde som en binær semafor, vi kan bruge den til at implementere gensidig udelukkelse.
I det følgende eksempel bruger vi en simpel binær semafor til at opbygge en tæller:
klasse CounterUsingMutex {privat Semaphore mutex; privat int count CounterUsingMutex () {mutex = ny Semaphore (1); tælle = 0; } ugyldig stigning () kaster InterruptedException {mutex.acquire (); this.count = this.count + 1; Tråd. Søvn (1000); mutex.release (); } int getCount () {returner this.count; } boolsk hasQueuedThreads () {return mutex.hasQueuedThreads (); }}
Når mange tråde forsøger at få adgang til tælleren på én gang, de bliver simpelthen blokeret i en kø:
@Test offentlig ugyldig nårMutexAndMultipleThreads_thenBlocked () kaster InterruptedException {int count = 5; ExecutorService executorService = Executors.newFixedThreadPool (count); CounterUsingMutex-tæller = ny CounterUsingMutex (); IntStream.range (0, count) .forEach (user -> executorService.execute (() -> {try {counter.increase ();} catch (InterruptedException e) {e.printStackTrace ();}})); executorService.shutdown (); assertTrue (counter.hasQueuedThreads ()); }
Når vi venter, får alle tråde adgang til tælleren, og der er ingen tråde tilbage i køen:
@Test offentlig ugyldighed givenMutexAndMultipleThreads_ThenDelay_thenCorrectCount () kaster InterruptedException {int count = 5; ExecutorService executorService = Executors.newFixedThreadPool (count); CounterUsingMutex-tæller = ny CounterUsingMutex (); IntStream.range (0, count) .forEach (user -> executorService.execute (() -> {try {counter.increase ();} catch (InterruptedException e) {e.printStackTrace ();}})); executorService.shutdown (); assertTrue (counter.hasQueuedThreads ()); Tråd. Søvn (5000); assertFalse (counter.hasQueuedThreads ()); assertEquals (count, counter.getCount ()); }
5. Konklusion
I denne artikel udforskede vi det grundlæggende i semaforer i Java.
Som altid er den fulde kildekode tilgængelig på GitHub.