说明: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 并发编程系列