乐趣区

关于java:Semaphore自白限流器用我就对了

大家好,我是 Semaphore,我的中文名字叫“信号量”,我来自 JUC(java.util.concurrent)家族。

咱们家族有很多优良的成员,比方:CountDownLatch(期待其余线程都执行完再执行某线程),CyclicBarrier(循环阻塞一组线程,直到某个事件达成),当然我也不比他们弱哦 罒 ω 罒。

以下是我的个人简历,心愿各位读者老爷们给个好评和三连,先在此谢过了~

根本信息

  • 姓名:Semaphore
  • 中文名:(计数)信号量
  • 出生日期:JDK 1.5
  • 籍贯:JUC(java.util.concurrent)
  • 用处:Java 中的一个同步器,与 CountDownLatch 和 CyclicBarrier 不同,Semaphore 是用来治理许可证的,线程在调用 acquire() 办法时,如果没有许可证,那么线程就会阻塞期待,直到有许可证时能力继续执行。许可证应用 release() 办法来公布(公布一个许可证),调用 acquire() 办法时,如果有证书会缩小许可证并继续执行前面的代码,如果没有证书只能阻塞期待许可证,而 Semaphore 在创立时会申明许可证的最大数量。

专业技能

我的专业技能就是“治理证书”,应用此技能 能够轻松的实现「限流」性能

什么是限流?

比方五一小长假快到了,到那时会有大量的人去各个景区玩耍,然而每个景区能包容的人是无限的,比方大西安的大唐芙蓉园,它的日承载量是 6 万人次,也就是说每天最多能让 6 万来这里玩耍,但五一的时候会来很多的人,比方忽然来了 10 万人,那这个时候就只能「限流」排队期待入园了。

也就说,大唐芙蓉园会让 6 万人先进去玩,残余的人在门口期待排队,当有人从外面进去的时候,才容许另一个排队的人进去。工作人员会把人数始终管制在 6 万人以下,这样做的目标是为了让玩耍的人有一个好的体验,不至于造成一些意外事故,比方踩踏事件什么的,肯定水平上保障了社会的稳固,也便于景区良好的口碑建设和日后的失常经营,而 这种排队限度最大人数的行为就是「限流」

再来举个例子,比方以车辆的限号来说,它也是限流的一种常见场景。这样做的益处,一方面是能够保护环境尽可能少一些碳排放,另一方面能无效的缓解上、上班顶峰时段的拥挤状况。尤其是在大西安,很难设想如果不限号,那么会堵成什么样?(PS:让本来本不富裕的生存更是雪上加霜 …)

咱们再从生存中的事例回到程序当中,假如一个程序只能为 10W 人提供服务,忽然有一天因为某个热点事件,造成了零碎短时间内的访问量迅速减少到了 50W,那么导致的间接后果是零碎解体,任何人都不能用零碎了,显然只有少人数能用远比所有人都不能用更合乎咱们的预期,因而这个时候咱们要应用「限流」了。

应用 Semaphore 实现限流

Semaphore 在创立的时候能够设置证书的数量,相当于设置了限流的最大值,再通过 release() 办法来发放证书,通过 acquire() 办法来阻塞并期待证书,这样就通过管制证书的形式来实现限流性能了。

我的项目教训

接下来,咱们应用代码的形式来演示 Semaphore 的应用。咱们以停车场的限流为例,假如整个停车场只有 2 个车位(车位虽少,但足矣阐明问题),但来停车的却有 5 辆车,显然车位不够用了,此时须要保障停车场最多只能有 2 辆车,接下来咱们应用 Semaphore 来实现车辆的限流性能,具体实现代码如下:

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Author:磊哥
 * By:Java 中文社群
 */
public class SemaphoreExample {
    // 创立信号量
    static Semaphore semaphore = new Semaphore(2);

    public static void main(String[] args) {

        // 创立 5 个固定的线程数
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        // 定义执行工作
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                // 拿到以后线程的名称
                String tname = Thread.currentThread().getName();
                System.out.println(String.format("老司机:%s,停车场外排队,工夫:%s",
                        tname, new Date()));
                try {
                    // 执行此行,让所有线程先排队期待进入停车场
                    Thread.sleep(100);
                    // 执行阻塞
                    semaphore.acquire();
                    System.out.println(String.format("老司机:%s,已进入停车场,工夫:%s",
                            tname, new Date()));
                    Thread.sleep(1000);
                    System.out.println(String.format("老司机:%s,来到停车场,工夫:%s",
                            tname, new Date()));
                    // 开释锁
                    semaphore.release();} catch (InterruptedException e) {e.printStackTrace();
                }
            }
        };

        // 执行工作 1
        threadPool.submit(runnable);

        // 执行工作 2
        threadPool.submit(runnable);

        // 执行工作 3
        threadPool.submit(runnable);

        // 执行工作 4
        threadPool.submit(runnable);

        // 执行工作 5
        threadPool.submit(runnable);

        // 等线程池工作执行完之后敞开
        threadPool.shutdown();}
}

以上代码的执行后果如下:

从上述的后果咱们能够看出,当有 5 辆车同时须要进入停车场时,因为停车场的停车位只有 2 个,所以停车场最多只能包容 2 辆车。此时咱们通过 Semaphore 的 acquire 办法(阻塞期待)和 release 办法(颁发一个证书)顺利的实现了限流的性能,让停车场的车辆数始终管制在 2 辆车以下(等于或小于 2 辆车)。

集体评估

(Semaphore)实现证书管制伎俩有两种,一种偏心模式和非偏心模式,当然为了执行的性能思考,默认状况下我采取的是非偏心的形式,具体实现可见源码:

public Semaphore(int permits) {sync = new NonfairSync(permits); // 非偏心模式
}

对于偏心模式和非偏心模式

所谓的偏心模式就是以调用 acquire() 的先后顺序来决定获取许可证的程序的,偏心模式遵循先进先出(FIFO)准则;而非偏心模式是抢占式的,也就是有可能一个新的获取线程恰好在一个许可证开释时失去了这个许可证,而后面还有期待的线程。

显然应用非偏心的模式性能更高,因为它会把许可证发放给刚好筹备好的线程,而不必再依据先后顺序去“叫号”了。

应用偏心模式

当然,你能够手动抉择应用偏心模式来运行 Semaphore,Semaphore 提供了两个构造函数,源码如下:

public Semaphore(int permits) {sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

如果想用偏心模式就能够应用第二个构造函数 Semaphore(int permits, boolean fair),将 fair 值设置为 true 就是偏心模式来获取证书了。

其余补充

我还提供了一些其余办法,用于实现更多的性能,详情如下:

  • int availablePermits():返回此信号量中以后可用的许可证数。
  • int getQueueLength():返回正在期待获取许可证的线程数。
  • boolean hasQueuedThreads():是否有线程正在期待获取许可证。
  • boolean isFair():查问 Semaphore 应用的是偏心模式还是非偏心模式,如果此信号量应用的是偏心模式则返回 true。
  • void release(int permits):开释给定数量的许可证,将其返回到信号量。
  • tryAcquire():从这个信号量取得许可证,只有在调用时能够应用该许可证。
  • tryAcquire(int permits):从这个信号量获取给定数量的许可证,只有在调用时全副可用。
  • tryAcquire(int permits, long timeout, TimeUnit unit):从该信号量获取给定数量的许可证,如果在给定的等待时间内全副可用,并且以后线程尚未 interrupted。
  • tryAcquire(long timeout, TimeUnit unit):如果在给定的等待时间内可用,并且以后线程尚未 达到 interrupted,则从该信号量获取许可。
  • void reducePermits(int reduction):缩小可用的许可证数量 reduction 个,它是 protected 办法。
  • Collection getQueuedThreads():返回所有期待获取许可证的线程汇合,它是 protected 办法。

总结

Semaphore 信号量是用来治理一组证书的,默认状况下它采取的是非偏心的形式来治理证书,这样做的目标是为了实现高性能。Semaphore 中蕴含了两个重要的办法:release() 办法公布一个许可证书;acquire() 办法阻塞并期待一个证书。当线程调用了 acquire() 办法只有领有了证书能力继续执行,因而能够应用 Semaphore 来实现限流。

关注公号「Java 中文社群」查看更多有意思、有涨常识的并发编程文章。

退出移动版