(原创加转载,在转载的解题方法上加上了部分原创优化)
题目如下:
我们提供一个类:
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
输出: "foobar"
解释: 这里有两个线程被异步启动。其中一个调用 foo() 方法, 另一个调用 bar() 方法,"foobar" 将被输出一次。
示例 2:输入: n = 2
输出: "foobarfoobar"
解释: "foobar" 将被输出两次。
目录
解题方法(转载)
原文地址:https://zhuanlan.zhihu.com/p/81626432
方案一:Semaphore
在该场景下有点类似红绿灯交替变换的情境,因此信号量成了首选思路:
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
Semaphore foo = new Semaphore(1);
Semaphore bar = new Semaphore(0);
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
foo.acquire();
printFoo.run();
bar.release();
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
bar.acquire();
printBar.run();
foo.release();
}
}
}
方案二:Lock(公平锁)
公平锁也是实现交替执行一个不错的选择:
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
Lock lock = new ReentrantLock(true);
volatile boolean permitFoo = true;
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; ) {
lock.lock();
try {
if(permitFoo) {
printFoo.run();
i++;
permitFoo = false;
}
}finally {
lock.unlock();
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; ) {
lock.lock();
try {
if(!permitFoo) {
printBar.run();
i++;
permitFoo = true;
}
}finally {
lock.unlock();
}
}
}
}
方案三:无锁
以上的公平锁方案完全可以改造成无锁方案:
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
volatile boolean permitFoo = true;
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; ) {
if(permitFoo) {
printFoo.run();
i++;
permitFoo = false;
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; ) {
if(!permitFoo) {
printBar.run();
i++;
permitFoo = true;
}
}
}
}
方案四:CyclicBarrier
在场景一中提过,CyclicBarrier更适合用在循环场景中,那么我们来试一下:
class FooBar {
private int n;
public FooBar(int n) {
this.n = n;
}
CyclicBarrier cb = new CyclicBarrier(2);
volatile boolean fin = true;
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
while(!fin);
printFoo.run();
fin = false;
try {
cb.await();
} catch (BrokenBarrierException e) {
}
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
try {
cb.await();
} catch (BrokenBarrierException e) {
}
printBar.run();
fin = true;
}
}
}
优化方法
针对上述解题方法进行部分优化
方案二优化(Lock公平锁)
方案二在N=5时会超时,两个方法间没有通信会出现第一次执行bar方法判断为false不输出释放锁,两者之间竞争cpu资源,并不能保证下一次一定是foo方法执行,还是可能是bar方法执行,所以程序执行过程会有cpu资源浪费。
法1:改善可以用condition,两者之间进行通信
class FooBar {
private int n;
private volatile boolean fooDone =false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
lock.lock();
for (int i = 0; i < n; i++) {
while(fooDone){
condition.await();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
printFoo.run();
fooDone = true;
condition.signalAll();
}
lock.unlock();
}
public void bar(Runnable printBar) throws InterruptedException {
lock.lock();
for (int i = 0; i < n; i++) {
while(!fooDone){
condition.await();
}
// printBar.run() outputs "bar". Do not change or remove this line.
printBar.run();
fooDone = false;
condition.signalAll();
}
lock.unlock();
}
}
法2:在bar方法的if(!permitFoo){}方法后,Thread.sleep(1);让它尽快释放CPU,减少占用CPU的时间。