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.