【Java&Go并发编程系列】7.信号量——Semaphore VS channel

说明:Java & Go 并发编程序列的文章,根据每篇文章的主题或细分标题,分别演示 Java 和 Go 语言当中的相关实现。更多该系列文章请查看:Java & Go 并发编程系列

信号量通常用于限制并发访问共享资源的线程数。线程在访问共享资源前,需要通过信号量获得许可,然后才可以继续访问。访问结束后需要把许可释放,以便其他线程可以访问。

代码场景:10个员工同时需要热饭,但是只有3个微波炉,未热饭的员工需要等微波炉空闲才能使用,假设热饭时间都是2秒。

以下代码使用了 CountDownLatch 和 sync.WaitGroup,关于两者的用法请回顾:等待一组并发任务完成——CountDownLatch VS sync.WaitGroup

「Java」Semaphore

Semaphore 的基本的使用方式:

  • 创建指定许可数量的信号量
  • acquire()获取许可
  • release()释放许可
public static void main(String[] args) throws InterruptedException {

    // 创建有3个许可的信号量,模拟3个微波炉
    Semaphore semaphore = new Semaphore(3);

    // 员工的数量
    int numberOfEmployee = 10;

    // 用于等待10个员工线程结束
    CountDownLatch countDownLatch = new CountDownLatch(numberOfEmployee);

    // 起10个线程模拟10个员工热饭
    for (int i = 0; i < numberOfEmployee; i++) {
        Thread t = new Thread(() -> {
            try {
                // 获取信号量,如果获取不到则阻塞在这里
                semaphore.acquire();
                System.out.printf("[%s] 第%s号员工开始热饭\n", LocalTime.now(), Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(2);
                System.out.printf("[%s] 第%s号员工结束热饭\n", LocalTime.now(), Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放信号量
                semaphore.release();
                countDownLatch.countDown();
            }
        });
        // 设置线程名称,用于打印对应的员工编号
        t.setName(String.valueOf(i));
        t.start();
    }

    // 阻塞直到 CountDownLatch 的计数为0
    countDownLatch.await();
    System.out.printf("所有员工完成了热饭\n");
    
}

输出结果:

[17:42:27.523] 第0号员工开始热饭
[17:42:27.523] 第1号员工开始热饭
[17:42:27.523] 第2号员工开始热饭
[17:42:29.537] 第0号员工结束热饭
[17:42:29.537] 第2号员工结束热饭
[17:42:29.537] 第1号员工结束热饭
[17:42:29.538] 第4号员工开始热饭
[17:42:29.538] 第3号员工开始热饭
[17:42:29.539] 第5号员工开始热饭
[17:42:31.543] 第3号员工结束热饭
[17:42:31.543] 第5号员工结束热饭
[17:42:31.543] 第4号员工结束热饭
[17:42:31.544] 第7号员工开始热饭
[17:42:31.544] 第6号员工开始热饭
[17:42:31.545] 第8号员工开始热饭
[17:42:33.549] 第7号员工结束热饭
[17:42:33.549] 第6号员工结束热饭
[17:42:33.549] 第8号员工结束热饭
[17:42:33.549] 第9号员工开始热饭
[17:42:35.553] 第9号员工结束热饭
所有员工完成了热饭
「Go」channel

Go 语言没有类似 Java Semaphore 的并发工具,但是通过缓冲通道可以方便的实现类似信号量的功能。

func main() {

	// 定义容量为3的缓冲通道,用以模拟最多只能同时容纳3名员工使用微波炉
	// 通道元素类型为struct{},表示空结构体
	bufferChan := make(chan struct{}, 3)

	// 用于等待10个员工 Goroutine 结束
	var wg sync.WaitGroup
	wg.Add(10)
	for i := 0; i < 10; i++ {
		go func(i int) {
			// defer 语句用于延迟执行代码,在函数正常或异常结束的时刻执行
			// 类似于 Java 的 finally
			defer func() {
				// 从通道接收一个元素值,表示释放一个微波炉的使用名额
				<-bufferChan
				wg.Done()
			}()
			// 往通道发送一个元素值,表示占用一个微波炉的使用名额
			// 如果通道已满,则阻塞在这里
			bufferChan <- struct{}{}
			fmt.Printf("[%s] 第%d号员工开始热饭\n", time.Now().Format(time.StampMilli), i)
			time.Sleep(time.Second * 2)
			fmt.Printf("[%s] 第%d号员工结束热饭\n", time.Now().Format(time.StampMilli), i)
		}(i)
	}
	wg.Wait()
	fmt.Printf("所有同事完成了热饭\n")
}
[Aug 16 08:03:01.706] 第9号员工开始热饭
[Aug 16 08:03:01.706] 第2号员工开始热饭
[Aug 16 08:03:01.706] 第4号员工开始热饭
[Aug 16 08:03:03.709] 第9号员工结束热饭
[Aug 16 08:03:03.709] 第1号员工开始热饭
[Aug 16 08:03:03.709] 第4号员工结束热饭
[Aug 16 08:03:03.709] 第2号员工结束热饭
[Aug 16 08:03:03.709] 第6号员工开始热饭
[Aug 16 08:03:03.709] 第3号员工开始热饭
[Aug 16 08:03:05.712] 第6号员工结束热饭
[Aug 16 08:03:05.712] 第5号员工开始热饭
[Aug 16 08:03:05.712] 第3号员工结束热饭
[Aug 16 08:03:05.712] 第7号员工开始热饭
[Aug 16 08:03:05.712] 第1号员工结束热饭
[Aug 16 08:03:05.712] 第0号员工开始热饭
[Aug 16 08:03:07.715] 第5号员工结束热饭
[Aug 16 08:03:07.715] 第8号员工开始热饭
[Aug 16 08:03:07.715] 第7号员工结束热饭
[Aug 16 08:03:07.715] 第0号员工结束热饭
[Aug 16 08:03:09.717] 第8号员工结束热饭
所有同事完成了热饭

更多该系列文章请查看:Java & Go 并发编程系列

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值