Semaphore、CountDownLatch和CyclicBarrier

这三者都是java并发包的工具类,提供了比synchronized更加高级的各种同步结构,可以实现更加丰富的多线程操作。
 

Semaphore

信号量,我们应该都在操作系统课程里学过,它是解决进程间通信和同步的常用工具,也是一种常见的模型。信号量是一个确定的二元组(s, q), s是正整型变量,q是初始状态为空的队列,s代表并发状态,操作系统利用信号量的状态s管理并发进程。如果s<=0,进程阻塞,如果s>0,进程继续执行。为了实现对s值的修改,操作系统提供了P、V操作原语,P的操作包括:s值减1,若s<=0,则进程阻塞,并将该进程插入到等待队列q中,V操作:s值加1,若s<=0,从等待队列中移出一个进程,解除其阻塞状态。

Java提供了经典信号量(Semaphore)的实现,它通常用于控制线程数来达到限制共享资源访问的目的。

下面用信号量实现生产者消费者模型来演示下用法:

public class SemaphoreDemo {

    private static volatile int count = 0 ;
    private static final Semaphore full = new Semaphore(5);
    private static final Semaphore empty = new Semaphore(0);
    private static final Semaphore mutex = new Semaphore(1);
    
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        threadPool.execute(() -> {
            while(true){
                try {
                    full.acquire();
                    mutex.acquire();
                    count++;
                    System.out.println("生产了1个资源, 目前还有" + count + "个资源");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    mutex.release();
                    empty.release();
                }
            }
        });

        threadPool.execute(() -> {
            while(true){
                try {
                    empty.acquire();
                    mutex.acquire();
                    count--;
                    System.out.println("消费了1个资源, 目前还有" + count + "个资源");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    mutex.release();
                    full.release();
                }
            }
        });
    }
}

full信号量限制了最多生产的资源数量,empty信号量限制了资源为空无法消费,mutex信号量相当于互斥锁,避免count读写不一致。总的来说,可以看出Semaphore就是个计数器,其基本逻辑基于acquire/release,acquire获取资源若获取不到则阻塞,release释放资源并唤醒阻塞的线程,信号量构造方法中还有个公平的参数这里没有演示。

 

CountDownLatch

作用是允许一个或多个线程等待直到其他线程中的某个操作完成。使用给定数值初始化CountDownLatch,调用countDown方法会减少计数器的值,调用await方法会阻塞直到当前计数达到零,之后释放所有等待的线程,但是注意计数器到0的时候不会自动重置(这也是和CyclicBarrier的区别之一)。

如果现在有这样一个场景,一批人叫了很多出租车,但是一车只能坐5个人,坐完后等下一辆车继续,用CountDownLatch如何实现?

代码如下:

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(6);
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for(int i = 0; i < 5; i++){
            executor.execute(() -> {
                System.out.println("First batch has executed!");
                latch.countDown();
            });
        }

        for(int i = 0; i < 5; i++){
            executor.execute(() -> {
                try {
                    latch.await();
                    System.out.println("Second batch has executed!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        while(latch.getCount() != 1){
            Thread.sleep(1000);
        }
        latch.countDown();
        System.out.println("Wait for first batch finish!");

    }
}

执行结果如下:

第一批执行完后,由于第二批代码中调用了await方法所以阻塞,直到coutdownlatch的计数器减到0为止。

 

CyclicBarrier

一种同步辅助工具,允许一组线程全部等待到达某个屏障。用这个工具类实现上述场景:

public class CyclicBarrierDemo {

    public static void main(String[] args) {
       //final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> new Thread(() -> System.out.println("Wait for batch finish!")).start());
       final CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> System.out.println("Wait for batch finish!"));

        for(int i = 0; i < 10; i++){
            new Thread(() -> {
                try {
                    System.out.println("The batch has executed!");
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

执行结果如下:

 

CyclicBarrier支持一个回调函数,每当一轮线程结束后,下一轮线程开始前,这个回调函数都会被调用一次,而且这个回调函数是执行在一个回合里最后执行await()的线程上。

注意上述代码中注释了一行,注释的写法意味着新开一个线程执行回调函数,那么回调函数会异步执行。

 

CountDownLatch和CyclicBarrier的区别

CountDownLatch是不可以重置的,所以无法重用;而CyclicBarrier则没有这种限制,可以重用。 CountDownLatch的基本操作组合是countDown/await。调用await的线程等待countDown直到足够的次数,不管是在一个线程还是多个线程里countDown。

CyclicBarrier的基本操作是await,当所有的线程都调用了await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier的重置都是自动发生的,如果我们调用reset方法,但还有线程在等待,就会导致等待线程被打扰,抛出BrokenBarrierException异常。 

转载于:https://www.cnblogs.com/morph/p/11165938.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值