Javaのセマフォ

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を返す 利用可能になるまでブロック ** 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時限 Smaphore

次に、Apache Commonsについて説明します。 TimedSemaphore. TimedSemaphore は、単純なセマフォとしていくつかの許可を許可しますが、一定期間内に、この期間の後にリセットされ、すべての許可が解放されます。

次のように 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結論

この記事では、Javaのセマフォーの基本を探りました。

いつものように、完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-concurrency[over on GitHub]から入手できます。