• JUC并发工具包之Semaphore


    Semaphore (JDK)

    我们使用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():如果还有可用的permit(构造方法传入的,表示限制的线程数量)则立即返回true,否则返回false,但是acquire()方法会以阻塞的方式获取一个permit。
    • release():释放一个permit。
    • availablePermits():返回当前可用的permit数量。

    我们来测试一下我们的登录队列,我们首先使用完所有的permit,然后再获取一个看看是否会被阻塞:

    @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());
    }
    

    现在我们logout()一下看看是否有可用的permit:

    @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());
    }
    

    结果显而易见。

    Timed Semaphore (Apache Commons)

    我们现在看看ApacheCommons下实现的TimedSemaphore。TimedSemaphore允许在既定的时间内维护一定数量的Semaphore(这段时间内和JDK实现的Semaphore效果一样),当时间过去后会释放所有的permits。

    我们可以使用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秒钟之内使用完所有的permit再次尝试获取的时候就会没有可用的permit:

    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());
    }
    

    但是把线程休眠1秒后,这时候semaphore会重置并释放所有的permits

    @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());
    }
    

    Semaphore vs. Mutex

    Mutex像是一个二进制的Semaphore,我们可以使用它来实现互斥。
    在下面的这个例子中,我们使用一个permit为1的Semaphore来构建一个计数器:

    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();
        }
    }    
    

    当大量线程同时来操作counter的时候,他们都会在队列中阻塞:

    @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());
    }
    

    我们把线程休眠一会后,所有的线程都将能操作counter,这时队列中就没有等待排队的线程了。

    @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());
    }
    

    CodeRepo

    完整代码在这

  • 相关阅读:
    shell命令运行符号&、;、&&区别
    绕过CDN查看真实IP的有效方法
    kali Linux各历史版本
    Referer详解
    HttpServletResponse详解
    XML中保留字符及实体引用
    PreparedStatement用法详解
    Abnormal build process termination IDEA启动报错
    解决stackOverflow打开慢的问题
    git报错---If no other git process is currently running...
  • 原文地址:https://www.cnblogs.com/mrcharleshu/p/13160685.html
Copyright © 2020-2023  润新知