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 ).
这样可以解决 生产者/消费者 问题,避免死锁.