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
实例。其中一个线程将会调用 foo()
方法,另一个线程将会调用 bar()
方法。
请设计修改程序,以确保 "foobar"
被输出 n
次。
示例:
输入1
n = 1
输出1
"foobar"
输入2
n = 3
输出2
"foobarfoobarfoobar"
解答
class FooBar {
private int n;
private boolean isFooed;
private Object lock; // lock
public FooBar(int n) {
this.n = n;
this.isFooed = false;
this.lock = new Object();
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printFoo.run() outputs "foo".
// Do not change or remove this line.
synchronized(lock){
while(isFooed){ // 重要! 否则超时
lock.wait();
}
printFoo.run();
isFooed = true;
lock.notifyAll();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printBar.run() outputs "bar".
// Do not change or remove this line.
synchronized(lock){
while(!isFooed){
lock.wait();
}
printBar.run();
isFooed = false;
lock.notifyAll();
}
}
}
public static void main(String[] args) {
FooBar fb = new FooBar(5);
Thread t1 = new Thread(new printFoo(fb));
// 一个线程 参数是实现了Runnable的对象
Thread t2 = new Thread(new printBar(fb));
// 第二个线程 参数是实现了Runnable的对象
// 题目要求这个两个线程共享FooBar对象fb
t2.start(); // 注意不能是run()
t1.start(); // 注意不能是run()
}
}
class printFoo implements Runnable{
FooBar fb ;
public printFoo(FooBar fb) {
this.fb = fb;
}
@Override
// Runnable 必须override run()
public void run() {
try {
fb.foo(() -> System.out.print("Foo"));
// lambda表达式实现override run() 打印"Foo"
// due to 题目要求foo方法要传入一个Runnable printFoo
// 而且printFoo.run()是打印"Foo"
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class printBar implements Runnable{
FooBar fb;
public printBar(FooBar fb) {
this.fb = fb;
}
@Override
// 同上
public void run() {
try {
fb.bar(() -> System.out.print("Bar"));
// 同上
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
解析
2个变量/对象保多证线程按照要求正确执行:
- lock 一个锁对象
保证任一时间内只能有一个线程在执行临界区代码. - isFooed 一个bool变量
用来指示foo
是否已打印,只有当foo
已经打印了才能打印bar
.
其实可以把 isFooed
视为一个上限为 1
的一个int信号量(semaphore), 将 foo()
视为生产者,将 bar()
视为消费者.
初始状态信号量为 0
(isFooed == false
).
只有当 isFooed == false
时生产者 foo()
才能运行, 在输出 "foo"
后需要 isFooed = true
(相当于 semaphore += 1
).
由于信号量上限为1, 所以信号量为 1
( isFooed == true
)时, 才能被消费者 bar()
所消费, 此时才能输出 "bar"
, 然后 isFooed = false
(相当于 semaphore -= 1
).
这样可以解决 生产者/消费者 问题,避免死锁.