多线程-按序打印

Published on May 8, 20213 min read

描述

点击查看原题

我们提供了一个类:

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