Skip to content

多线程-按序打印

Published: at 17:02:50

描述

点击查看原题

我们提供了一个类:

public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}

三个不同的线程 A、B、C 将会共用一个 Foo 实例。

请设计修改程序,以确保 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();
        }
    }

优点:

缺点:

解法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();
    }
}

优点:

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