题目

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

解析思路

需要有一个变量控制两个线程进行交替打印,即必须是先foo,然后bar,再然后foo,再然后bar…(不能foo函数还没执行就先执行bar函数,或者foo函数一直执行) 那么就可以用类似锁的思路去实现,并且这个锁要区分两种不同类型的线程,既可以理解成此题是一个变种的“消费者-生产者”问题。 java中,可以用Lock中的Condition来实现。

前置知识

ReentrantLock是实现了Lock接口的类,它是通过CAS+AQS来实现的。当前线程执行ReentrantLock对象的lock()方法,如果获取锁成功,则ReentrantLock对象中的state+1,在该线程没释放该锁之前其他线程不能重复获取该ReentrantLock对象的锁,会被阻塞放入到ReentrantLock的阻塞队列中(这个队列是一个双向链表构成的,线程会被包装成一个Node对象插入这个双向链表中)而ReentrantLock还有一个等待队列,当前获取锁的线程执行Condition的await()方法时会将当前线程加入到ReentrantLock对象的等待队列中。并且此时会自动释放掉获取的锁,其他在阻塞队列中的线程可以抢夺锁了。(等待队列是单项链表构成,但是跟synchronized的monitor中的等待队列相比(一个monitor对象只能有一个等待队列),一个condition的等待队列可以有多个等待队列)

ReentrantLock的原理内附一个通过ReentrantLock以及Condition实现的生产者-消费者实例代码。 ReentrantLock的Condition实现原理 添加链接描述

代码

private int n;

Lock lock = new ReentrantLock();

Condition conFoo = lock.newCondition();

boolean flag = false;

public FooBar(int n) {

this.n = n;

}

public void foo(Runnable printFoo) throws InterruptedException {

for (int i = 0; i < n; i++) {

lock.lock();

try

{

while(flag){

conFoo.await();

}

// printFoo.run() outputs "foo". Do not change or remove this line.

printFoo.run();

conFoo.signal();

flag = true;

}catch(InterruptedException ie){

ie.printStackTrace();

}finally{

lock.unlock();

}

}

}

public void bar(Runnable printBar) throws InterruptedException {

for (int i = 0; i < n; i++) {

lock.lock();

try{

if(!flag){

conFoo.await();

}

// printBar.run() outputs "bar". Do not change or remove this line.

printBar.run();

conFoo.signal();

flag = false;

}catch(InterruptedException ie){

ie.printStackTrace();

}finally{

lock.unlock();

}

}

}

注意点

1.当前线程获取ReentrantLock对象的锁后,如果执行Condition的await方法,被阻塞时,会自动释放当前的锁。其他在阻塞队列中的线程会被唤醒,去争夺锁。

2.ReentrantLock对象获取锁的lock()方法要写在try…catch之外,而释放锁的unlock()方法要写在finally中。

原因:

加入lock()写在try…catch之内,如下

try{

lock.lock()

if(!flag){

conFoo.await();

}

// printBar.run() outputs "bar". Do not change or remove this line.

printBar.run();

conFoo.signal();

flag = false;

}

catch(InterruptedException ie){

ie.printStackTrace();

}

finally{

lock.unlock();

}

那么如果lock()方法执行异常,最终会执行finally的unlock()方法,但是当前线程又没有对ReentrantLock加锁成功,则会报错IllegalStateException。

释放锁unlock()方法要写在finally中是因为,如果unlock()方法直接放在try代码块中的最后,如果方法块执行中其他代码出现异常,就不会执行unlock()方法,则这个锁就永远不会被释放,产生死锁了。

3.throws InterrruptedException是Condition对象的await操作抛出的,不是ReentrantLock对象执行lock()方法抛出的。

4.这里判断flag是否为true的逻辑是用while的,而不是用if,

是防止出现提前执行signal,让其他等待队列中不应该释放的线程被提前释放。 并且当满足条件时即跳出循环,比if也不会造成效率的浪费。

好文链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。