Semaphore和CountDownLatch(信号量和计数器)

本文详细介绍了Java并发编程中的Semaphore(信号量)和CountDownLatch的使用。Semaphore用于限制并发访问的线程数量,常用于流量控制;而CountDownLatch则用于线程间的同步,直到计数器归零才释放所有线程。文中通过实例展示了它们各自的工作原理和应用场景,如Semaphore在数据库连接池中的应用,以及CountDownLatch在确保线程执行顺序上的作用。
摘要由CSDN通过智能技术生成

Semaphore和CountDownLatch

一、Semaphore (信号量)

    说简单点,Semaphore维护了一个许可集合,在创建Semaphore的时候,设置上许可数,每条线程在只有在获得一个许可的时候才可以继续往下执行逻辑(申请一个许可,则Semaphore的许可池中减少一个许可),没有获得许可的线程会进入阻塞状态。

例如:

public static void main(String[] args) {
        //创建一个Semaphore 有5条许可
        final Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 10; i++) {
            final int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //申请一个许可  许可池-1   若许可池为0则申请许可失败,阻塞线程
                        semaphore.acquire();
                        System.out.print(finalI);
                        //模仿耗时操作
                        Thread.sleep(1000);
                        //释放一个许可  许可池+1
                        semaphore.release();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

然后看log的打印:

12-19 11:06:52.720 13842-13978/lbx.myapplication E/lbxlog: 0
12-19 11:06:52.720 13842-13981/lbx.myapplication E/lbxlog: 3
12-19 11:06:52.720 13842-13979/lbx.myapplication E/lbxlog: 1
12-19 11:06:52.720 13842-13980/lbx.myapplication E/lbxlog: 2
12-19 11:06:52.720 13842-13982/lbx.myapplication E/lbxlog: 4
//这里注意一下  仔细对比时间
12-19 11:06:53.720 13842-13983/lbx.myapplication E/lbxlog: 5
12-19 11:06:53.720 13842-13985/lbx.myapplication E/lbxlog: 7
12-19 11:06:53.720 13842-13987/lbx.myapplication E/lbxlog: 9
12-19 11:06:53.720 13842-13986/lbx.myapplication E/lbxlog: 8
12-19 11:06:53.720 13842-13984/lbx.myapplication E/lbxlog: 6

看了上面的log,发现前五个是52秒左右打印出来的,而后五个是53秒左右打印出来的,因为许可池里只有5个许可,前五条线程把5个许可占用了,后五条线程需要等到有许可后才会继续执行逻辑。

//释放1个许可
semaphore.release();
//使acquire()后正在等待的线程不可被终止
semaphore.acquireUninterruptibly();
//获取当前可用的许可数量,并把可用的许可数置0
semaphore.drainPermits();
//是否有正在等待的线程
semaphore.hasQueuedThreads();
//获取正在等待的线程数量
semaphore.getQueueLength();
//获取当前可用的许可数量
semaphore.availablePermits();
 

二、信号量的另一种形式:非公平信号量

​ 非公平信号量,先运行的线程不一定可以申请到许可,后运行的线程不一定不可以申请到许可。

/创建5个公平信号量
Semaphore semaphore = new Semaphore(5,true);
//尝试获得许可,并返回获取结果   if(acquireSuc)...else...
boolean acquireSuc = semaphore.tryAcquire();
boolean acquireSuc = semaphore.tryAcquire(2);
//在3秒内尝试获得1个许可,并返回获取结果
boolean acquireSuc = semaphore.tryAcquire(3, TimeUnit.SECONDS);
//在3秒内尝试获得2个许可,并返回获取结果
boolean acquireSuc = semaphore.tryAcquire(2, 3, TimeUnit.SECONDS);

三、CountDownLatch的应用

这个东西和信号量正相反,CountDownLatch的内部维护着一个计数器,同时只有一个线程才可对计数器进行操作,当计数器为0的时候,释放所有阻塞的线程。

例如:

public static void main(String[] args) {
        //4条线程
        int threadCount = 4;
        //创建一个拥有5个计数器的countDownLatch
        final CountDownLatch countDownLatch = new CountDownLatch(5);
        for (int i = 0; i < threadCount; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //使计数器-1
                    countDownLatch.countDown();
                    System.out.println("计数器还剩" + countDownLatch.getCount());
                }
            }).start();
        }
 
        try {
            //在这里阻塞  只有计数器为0的时候这行代码才会释放
             countDownLatch.await();
            } catch (InterruptedException e) {
             e.printStackTrace();
            }
 
  System.out.println("执行完成啦!"); 
}

12-19 11:52:25.350 22929-23074/lbx.myapplication I/System.out: 计数器还剩4
12-19 11:52:25.350 22929-23073/lbx.myapplication I/System.out: 计数器还剩3
12-19 11:52:25.350 22929-23075/lbx.myapplication I/System.out: 计数器还剩2
12-19 11:52:25.350 22929-23076/lbx.myapplication I/System.out: 计数器还剩1

因为计数器没有到0,所以“执行完成”这句话没有打印,将线程改成5条:

//5线程
        int threadCount = 5;
12-19 11:58:46.570 30347-30471/lbx.myapplication I/System.out: 计数器还剩4
12-19 11:58:46.570 30347-30473/lbx.myapplication I/System.out: 计数器还剩3
12-19 11:58:46.570 30347-30472/lbx.myapplication I/System.out: 计数器还剩2
12-19 11:58:46.570 30347-30474/lbx.myapplication I/System.out: 计数器还剩1
12-19 11:58:46.570 30347-30475/lbx.myapplication I/System.out: 计数器还剩0
12-19 11:58:46.570 30347-30347/lbx.myapplication I/System.out: 执行完成啦!

这时候完整的流程就执行完了。常用方法:

//使线程进入阻塞状态,计数为0的时候释放线程
countDownLatch.await();
//使线程进入阻塞状态,计数为0的时候释放线程或者阻塞到3秒的时候自动释放线程
countDownLatch.await(3, TimeUnit.SECONDS);
//使计数减1
countDownLatch.countDown();
//获取当前的计数
countDownLatch.getCount();

四、区别

  • 前者为信号量,信号量只有持有信号的线程才能够执行,其他没有持有信号的就进入阻塞状态,等待持有线程执行完成释放信号。
  • 后者为计数器,只有计数器为0的时候才释放所有线程,否则持有计数器的线程都将进入阻塞状态。

五、使用场景

CountDownLatch:假设计数器的值为2,线程A调用await()方法之后,A线程就进入了等待状态,之后其它线程中执行countDown(),计数器就会-1,该操作线程继续执行,当计数器从2编程0,线程A继续执行。

Semaphore就是信号量,Semaphore可以阻塞线程并且可以控制同时访问线程的个数,通过acquire()获取一个许可,如果没有获取到就继续等待,通过release()释放一个许可。Semaphore和锁有点类似,都可以控制对某个资源的访问权限。

CountDownLatchSemaphore通常和线程池配合使用。Semaphore适合控制并发数,CountDownLatch比较适合保证线程执行完后再执行其他处理,因此模拟并发时,使用两者结合起来是最好的。

Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。 假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遥不可及~~斌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值