描述
我们提供了一个类:
public class Foo {
public void first() { print("first"); }
public void second() { print("second"); }
public void third() { print("third"); }
}
三个不同的线程 A、B、C 将会共用一个 Foo
实例。
- 一个将会调用
first()
方法 - 一个将会调用
second()
方法 - 还有一个将会调用
third()
方法
请设计修改程序,以确保 second()
方法在 first()
方法之后被执行,third()
方法在 second()
方法之后被执行。
示例
示例 1:
输入: [1,2,3]
输出: "firstsecondthird"
解释:
有三个线程会被异步启动。
输入 [1,2,3] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 second() 方法,线程 C 将会调用 third() 方法。
正确的输出是 "firstsecondthird"。
示例 2:
输入: [1,3,2]
输出: "firstsecondthird"
解释:
输入 [1,3,2] 表示线程 A 将会调用 first() 方法,线程 B 将会调用 third() 方法,线程 C 将会调用 second() 方法。
正确的输出是 "firstsecondthird"。
提示
- 尽管输入中的数字似乎暗示了顺序,但是我们并不保证线程在操作系统中的调度顺序。
- 你看到的输入格式主要是为了确保测试的全面性。
题解
解法1:使用volatile
修饰的变量
用volatile
修饰的变量来标识每个方法的执行情况。输出second、third的方法需要等待前一个方法触发指定的条件方可执行。
代码:
class Foo_1 {
private volatile int index = 0;
public Foo_1() {
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
index = 1;
}
public void second(Runnable printSecond) throws InterruptedException {
while(index < 1){
// 自旋,等待first方法执行
}
printSecond.run();
index = 2;
}
public void third(Runnable printThird) throws InterruptedException {
while(index < 2){
// 自旋,等待second方法执行
}
printThird.run();
}
}
优点:
- 代码改动少,实现简单
缺点:
- second、third使用while自旋,会占用CPU,如果方法first不执行,则后面的方法会一直占用 CPU。
解法2:使用CountDownLatch
CountDownLatch可以用来计数,通过设置为1,为second、third设置两个计数,second方法可执行的计数需要由first方法扣减,third方法可执行计数需要由second方法扣减触发,
代码:
class Foo_2 {
// 初始化计数
CountDownLatch cdl_second = new CountDownLatch(1);
CountDownLatch cdl_third = new CountDownLatch(1);
public Foo_2() {
}
public void first(Runnable printFirst) throws InterruptedException {
// printFirst.run() outputs "first". Do not change or remove this line.
printFirst.run();
// 减计数,second方法才可以执行
cdl_second.countDown();
}
public void second(Runnable printSecond) throws InterruptedException {
// 等待计数为0
cdl_second.await();
// printSecond.run() outputs "second". Do not change or remove this line.
printSecond.run();
// 减计数,third方法才可以执行
cdl_third.countDown();
}
public void third(Runnable printThird) throws InterruptedException {
// 等待计数为0
cdl_third.await();
// printThird.run() outputs "third". Do not change or remove this line.
printThird.run();
}
}
优点:
- 代码改动少,实现简单,没有自旋不会占用多余的CPU
CountDownLatch 一般都会和CyclicBarrier进行比较,那这里可以用CyclicBarrier实现么?CyclicBarrier主要的方法就只有一个await()方法,多个线程时由最后一个线程来唤醒前面等待的线程,这里没办法确定最后一个线程是谁。
解法3:使用Semaphore
和CountDownLatch的方案类似,只不过这里是由前一个方法来释放资源,后面的方法才可以执行。
先通过构造方法占用资源,second、third方法请求acquire()就会等待。
然后first方法执行,释放一个资源。
之后second方法执行,释放一个资源,最后second方法执行。
代码:
class Foo_3 {
// 1:实现互斥锁一样的效果
Semaphore sp_second = new Semaphore(1,true);
Semaphore sp_third = new Semaphore(1,true);
public Foo_3() {
try{
// 先通过构造方法占用资源,second、third方法请求acquire()就会等待
sp_second.acquire();
sp_third.acquire();
}catch(InterruptedException e){
e.printStackTrace();
}
}
public void first(Runnable printFirst) throws InterruptedException {
printFirst.run();
// 释放资源,second方法才可以获得
sp_second.release();
}
public void second(Runnable printSecond) throws InterruptedException {
sp_second.acquire();
printSecond.run();
// 释放资源,third方法才可以获得
sp_third.release();
}
public void third(Runnable printThird) throws InterruptedException {
sp_third.acquire();
printThird.run();
}
}
解法4:使用条件锁
使用ReentrantLock结合Condition,来控制每个方法的执行。
代码:
static class Foo_4 {
// 标识每个方法的执行情况
private volatile int flag = 1;
ReentrantLock lock;
Condition c1;
Condition c2;
Condition c3;
public Foo_4() {
// 初始化锁
lock = new ReentrantLock(true);
c1 = lock.newCondition();
c2 = lock.newCondition();
c3 = lock.newCondition();
}
public void first(Runnable printFirst) throws InterruptedException{
try{
lock.lock();
while(flag != 1){
c1.await();
}
printFirst.run();
flag <<= 1;
c2.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void second(Runnable printSecond) throws InterruptedException{
try{
lock.lock();
while(flag != 2){
c2.await();
}
printSecond.run();
flag <<= 1;
c3.signal();
}finally{
lock.unlock();
}
}
public void third(Runnable printThird) throws InterruptedException{
try{
lock.lock();
while(flag != 4){
c3.await();
}
printThird.run();
c1.signal();
}finally{
lock.unlock();
}
}
}
测试方法
public static void main(String[] args) throws InterruptedException{
Foo_4 foo = new Foo_4();
new Thread(()-> {
try{
foo.second(()-> System.out.println("second"));
}catch(InterruptedException e){
e.printStackTrace();
}
}).start();
new Thread(()-> {
try{
foo.first(()-> System.out.println("first"));
}catch(InterruptedException e){
e.printStackTrace();
}
}).start();
new Thread(()-> {
try{
foo.third(()-> System.out.println("third"));
}catch(InterruptedException e){
e.printStackTrace();
}
}).start();
}