1115. 交替打印 FooBar
难度中等
题目描述
给你一个类:
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程将会共用一个 FooBar 实例:
- 线程 A 将会调用
foo()方法,而 - 线程 B 将会调用
bar()方法
请设计修改程序,以确保 "foobar" 被输出 n 次。
示例 1:
输入:n = 1 输出:"foobar" 解释:这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,"foobar" 将被输出一次。
示例 2:
输入:n = 2 输出:"foobarfoobar" 解释:"foobar" 将被输出两次。
提示:
1 <= n <= 1000
解法
方法一:多线程 + 信号量
思考
两线程须严格交替打印 \(n\) 次。一对信号量 \(f\)、 \(b\) 初值分别为 \(1\) 与 \(0\),保证 \(\textit{foo}\) 先获得许可;每次打印后释放对方、收回自己,下一轮才能继续。循环 \(n\) 次即可得到 foobar 重复 \(n\) 遍。
我们用两个信号量 \(f\) 和 \(b\) 来控制两个线程的执行顺序,其中 \(f\) 初始值为 \(1\),而 \(b\) 初始值为 \(0\),表示线程 \(A\) 先执行。
当线程 \(A\) 执行时,首先会执行 \(f\) 的 \(\textit{acquire}\) 操作,此时 \(f\) 的值变为 \(0\),线程 \(A\) 获得了 \(f\) 的使用权,可以执行 \(\textit{foo}\) 函数,然后执行 \(b\) 的 \(\textit{release}\) 操作,此时 \(b\) 的值变为 \(1\),线程 \(B\) 获得了 \(b\) 的使用权,可以执行 \(\textit{bar}\) 函数。
当线程 \(B\) 执行时,首先会执行 \(b\) 的 \(\textit{acquire}\) 操作,此时 \(b\) 的值变为 \(0\),线程 \(B\) 获得了 \(b\) 的使用权,可以执行 \(\textit{bar}\) 函数,然后执行 \(f\) 的 \(\textit{release}\) 操作,此时 \(f\) 的值变为 \(1\),线程 \(A\) 获得了 \(f\) 的使用权,可以执行 \(\textit{foo}\) 函数。
因此,我们只需要循环 \(n\) 次,每次执行 \(\textit{foo}\) 和 \(\textit{bar}\) 函数时,先执行 \(\textit{acquire}\) 操作,再执行 \(\textit{release}\) 操作即可。
时间复杂度 \(O(n)\),空间复杂度 \(O(1)\)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | |