LeetCode|交替打印FooBar

LeetCode|交替打印FooBar


题目连接,如下,输入一个n,交替打印Foo和Bar,各n次。

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");
    }
  }
}

看到这道题的第一反应,要么是锁了,要么是自旋了。首先,先试试自旋,如果自旋可以,就不用锁了。很快代码如下,很显然运行后就立即报错了Thrown exception java.lang.IllegalMonitorStateException,这个错误也算很常见了,wait方法只能由占有锁(严格来说是当前线程拥有对象的监视器)的线程主动调用才能生效:

private volatile boolean flag = false;
    public void foo(Runnable printFoo) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (flag) {
    
    
                wait();
            }
            // printFoo.run() outputs "foo". Do not change or remove this line.
            printFoo.run();
            flag = true;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (!flag) {
    
    
                wait();
            }
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            flag = false;
        }
    }

于是加了判断,只有持有当前对象的监视器的线程才能进行wait,于是采用holdsLock方法进行判断,这下子测试用例可以过了,但在n=5的时候,就超时了。:

private volatile boolean flag = false;
    public void foo(Runnable printFoo) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (flag) {
    
    
                if (Thread.holdsLock(this)) {
    
    
                    wait();
                }
            }
            // printFoo.run() outputs "foo". Do not change or remove this line.
            printFoo.run();
            flag = true;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (!flag) {
    
    
                if (Thread.holdsLock(this)) {
    
    
                    wait();
                }
            }
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            flag = false;
        }
    }

既然如此,我干脆不用wait了,直接自旋,毫无疑问又是超时了。仔细想想超时原因,其实,自旋的目的不就是为了让不符合条件的打印先缓一缓吗但问题是,不符合条件的打印进入自旋了,**符合条件的线程就会马上执行吗?**我们知道,多个线程的调度是随机的,谁能抢到CPU的控制权就执行谁,那么就可能存在一种情况,进入自旋的线程总能先一步的抢到CPU的控制权,然后总是执行,才会造成时间上的不断消费。

private volatile boolean flag = false;
    public void foo(Runnable printFoo) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (flag) {
    
    
               
            }
            // printFoo.run() outputs "foo". Do not change or remove this line.
            printFoo.run();
            flag = true;
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (!flag) {
    
    
              
            }
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            flag = false;
        }
    }

知道原因,是否有一种方法可以在不符合条件时,让该线程让出CPU的控制权吗?说到这里很多人大概能猜到了。那就是Thread.yield()

class FooBar {
    
    
    private int n;

    public FooBar(int n) {
    
    
        this.n = n;
    }

    private volatile boolean flag = false;
    public void foo(Runnable printFoo) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (flag) {
    
    
                Thread.yield();
            }
            // printFoo.run() outputs "foo". Do not change or remove this line.
            printFoo.run();
            flag = true;
        }
    }
    
    public void bar(Runnable printBar) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            while (!flag) {
    
    
                Thread.yield();
            }
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            flag = false;
        }
    }
}

耗时和所占空间如下:

执行用时:25 ms
内存消耗:39.4 MB

那通过锁的方式呢?锁的话又重入锁、同步锁。这里试着用重入锁进行了实现,原理差不多,利用Condition创建了两个条件队列。

每次打印后,更改flag 标识,并唤醒对方法条件队列中的线程,当不符合条件的线程获得锁时,根据flag,会调用await进入等待队列 :

class FooBar {
    
    
    private int n;

    public FooBar(int n) {
    
    
        this.n = n;
    }

    private static Lock lock = new ReentrantLock();
    private static Condition foo = lock.newCondition();
    private static Condition bar = lock.newCondition();
    private static boolean flag = true;
    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.
            lock.lock();
            if (!flag) {
    
    
                foo.await();
            }
            printFoo.run();
            flag = false;
            bar.signal();
            lock.unlock();
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            lock.lock();
            if (flag) {
    
    
                bar.await();
            }
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            flag = true;
            foo.signal();
            lock.unlock();
        }
    }
}

耗时和所占空间如下:

执行用时:23 ms
内存消耗:39.8 MB

关于这道题其实还有很多丰富的解法,还可以采用重入锁里头的信号量Semaphore进行实现,也可以采用AtomicInteger原子类进行乐观锁的实现,不过运行效果也比较差强人意,跟第一种方法自旋的时间和空间花费基本一致。

也可以借助synchronize实现,如果不想使用重入锁里面的signal和awati方法,亦可采用object里头的wait和notify方法。

也有小机灵鬼,直接采用同步阻塞队列,申明一个空间为1的队列,利用put和add的特性,put时候,如果空间不够用,会进入条件队列进行等待,take的时如果没有东西可取,同样也进入条件队列进行等待:

执行用时:
22 ms
内存消耗:
40.2 MB
private BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>(1);
   public void foo(Runnable printFoo) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            queue.put(true);
            printFoo.run();
            queue.put(true);
        }
    }

    public void bar(Runnable printBar) throws InterruptedException {
    
    
        for (int i = 0; i < n; i++) {
    
    
            queue.take();
            printBar.run();
            queue.take();
        }
    }

猜你喜欢

转载自blog.csdn.net/legendaryhaha/article/details/107447800
今日推荐