我们提供一个类:

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 ).

这样可以解决 生产者/消费者 问题,避免死锁.