자바의 세마포어

1. 개요

이 빠른 자습서에서는 Java의 세마포어 및 뮤텍스의 기본 사항을 살펴 봅니다.

2. 세마포어

java.util.concurrent.Semaphore 부터 시작하겠습니다 . 세마포어를 사용하여 특정 리소스에 액세스하는 동시 스레드 수를 제한 할 수 있습니다.

다음 예에서는 시스템의 사용자 수를 제한하는 간단한 로그인 대기열을 구현합니다.

class LoginQueueUsingSemaphore { private Semaphore semaphore; public LoginQueueUsingSemaphore(int slotLimit) { semaphore = new Semaphore(slotLimit); } boolean tryLogin() { return semaphore.tryAcquire(); } void logout() { semaphore.release(); } int availableSlots() { return semaphore.availablePermits(); } }

다음 방법을 어떻게 사용했는지 확인하십시오.

  • tryAcquire () – 허가를 즉시 사용할 수 있으면 true를 반환하고 그렇지 않으면 false를 반환하지만 acquire () 는 허가를 획득하고 사용할 수있을 때까지 차단합니다.
  • release () – 허가 해제
  • availablePermits () – 사용 가능한 현재 허가 수를 반환합니다.

로그인 대기열을 테스트하기 위해 먼저 제한에 도달하고 다음 로그인 시도가 차단되는지 확인합니다.

@Test public void givenLoginQueue_whenReachLimit_thenBlocked() { int slots = 10; ExecutorService executorService = Executors.newFixedThreadPool(slots); LoginQueueUsingSemaphore loginQueue = new LoginQueueUsingSemaphore(slots); IntStream.range(0, slots) .forEach(user -> executorService.execute(loginQueue::tryLogin)); executorService.shutdown(); assertEquals(0, loginQueue.availableSlots()); assertFalse(loginQueue.tryLogin()); }

다음으로 로그 아웃 후 사용 가능한 슬롯이 있는지 확인합니다.

@Test public void givenLoginQueue_whenLogout_thenSlotsAvailable() { int slots = 10; ExecutorService executorService = Executors.newFixedThreadPool(slots); LoginQueueUsingSemaphore loginQueue = new 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. 시간이 지정된 세마포

다음으로 Apache Commons TimedSemaphore에 대해 설명합니다. TimedSemaphore 는 간단한 Semaphore로 여러 허가를 허용하지만 주어진 기간 내에이 기간이 지나면 시간이 재설정되고 모든 허가가 해제됩니다.

TimedSemaphore 를 사용하여 다음과 같이 간단한 지연 대기열을 만들 수 있습니다 .

class DelayQueueUsingTimedSemaphore { private TimedSemaphore semaphore; DelayQueueUsingTimedSemaphore(long period, int slotLimit) { semaphore = new TimedSemaphore(period, TimeUnit.SECONDS, slotLimit); } boolean tryAdd() { return semaphore.tryAcquire(); } int availableSlots() { return semaphore.getAvailablePermits(); } }

기간이 1 초인 지연 대기열을 사용하고 1 초 내에 모든 슬롯을 사용한 후에는 사용할 수있는 것이 없어야합니다.

public void givenDelayQueue_whenReachLimit_thenBlocked() { int slots = 50; ExecutorService executorService = Executors.newFixedThreadPool(slots); DelayQueueUsingTimedSemaphore delayQueue = new DelayQueueUsingTimedSemaphore(1, slots); IntStream.range(0, slots) .forEach(user -> executorService.execute(delayQueue::tryAdd)); executorService.shutdown(); assertEquals(0, delayQueue.availableSlots()); assertFalse(delayQueue.tryAdd()); }

그러나 잠시 자고 나면 세마포어가 허가를 재설정하고 해제해야합니다 .

@Test public void givenDelayQueue_whenTimePass_thenSlotsAvailable() throws InterruptedException { int slots = 50; ExecutorService executorService = Executors.newFixedThreadPool(slots); DelayQueueUsingTimedSemaphore delayQueue = new DelayQueueUsingTimedSemaphore(1, slots); IntStream.range(0, slots) .forEach(user -> executorService.execute(delayQueue::tryAdd)); executorService.shutdown(); assertEquals(0, delayQueue.availableSlots()); Thread.sleep(1000); assertTrue(delayQueue.availableSlots() > 0); assertTrue(delayQueue.tryAdd()); }

4. 세마포 대 뮤텍스

뮤텍스는 이진 세마포어와 유사하게 작동하며 상호 배제를 구현하는 데 사용할 수 있습니다.

다음 예에서는 간단한 이진 세마포어를 사용하여 카운터를 만듭니다.

class CounterUsingMutex { private Semaphore mutex; private int count; CounterUsingMutex() { mutex = new Semaphore(1); count = 0; } void increase() throws InterruptedException { mutex.acquire(); this.count = this.count + 1; Thread.sleep(1000); mutex.release(); } int getCount() { return this.count; } boolean hasQueuedThreads() { return mutex.hasQueuedThreads(); } }

많은 스레드가 한 번에 카운터에 액세스하려고 하면 대기열에서 차단됩니다 .

@Test public void whenMutexAndMultipleThreads_thenBlocked() throws InterruptedException { int count = 5; ExecutorService executorService = Executors.newFixedThreadPool(count); CounterUsingMutex counter = new CounterUsingMutex(); IntStream.range(0, count) .forEach(user -> executorService.execute(() -> { try { counter.increase(); } catch (InterruptedException e) { e.printStackTrace(); } })); executorService.shutdown(); assertTrue(counter.hasQueuedThreads()); }

기다릴 때 모든 스레드는 카운터에 액세스하고 큐에 남아있는 스레드는 없습니다.

@Test public void givenMutexAndMultipleThreads_ThenDelay_thenCorrectCount() throws InterruptedException { int count = 5; ExecutorService executorService = Executors.newFixedThreadPool(count); CounterUsingMutex counter = new CounterUsingMutex(); IntStream.range(0, count) .forEach(user -> executorService.execute(() -> { try { counter.increase(); } catch (InterruptedException e) { e.printStackTrace(); } })); executorService.shutdown(); assertTrue(counter.hasQueuedThreads()); Thread.sleep(5000); assertFalse(counter.hasQueuedThreads()); assertEquals(count, counter.getCount()); }

5. 결론

이 기사에서 우리는 자바에서 세마포어의 기초를 탐구했다.

항상 그렇듯이 전체 소스 코드는 GitHub에서 사용할 수 있습니다.