leetcode1115. 交替打印 FooBar

题目

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{
            while(!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也不会造成效率的浪费。

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值