这道阿里笔试题,你懂吗?

这道阿里笔试题,你懂吗?

Scroll Down

咳咳,肥壕最近...

碰上阿里的一道笔试题目,虽说题目不是非常难,但对于平时疏于练习的肥壕来说,还是花费了些许时间。

今天就特意花点时间整理一下,方便以后大家碰到这类问题能得心应手,手撕面试官。

写一个多线程,3 个线程,第一个线程打印 One,第二个打印 Two,第三个打印 Three,同时启动三个线程,要求按照 one two three 顺序打印出来

题目分析

题目本意比较简单,主要考察多线程之间的通信。3 个线程的启动是随机的,那如何让这 3 个线程按顺序打印呢。

我们知道,线程之间无非就两种关系:竞争协同

  • 竞争:每个线程去竞争打印,判断当前状态是否轮到自己打印。
  • 协同:当前线程打印后,通知下一个等待的线程。

代码实现

先讲一下竞争型的实现方案,3 个线程启动后会去抢占锁,获取锁的线程会判断当前是否满足打印的条件,如果不满足则释放锁, 3 个线程再去抢占锁,如此重复。

判断条件的实现是:维护一个 state,初始值为 0,三个线程绑定对应的值 num 分别是: 0,1,2,当 state % 3 == num 才能满足打印的条件,并且 state自增加 1 。

很显然,多线程竞争型的缺点是,线程抢占锁完全是由 CPU 调度,可能会出现不该打印的线程先被调度一直抢占锁,而该打印的线程一直未被调度,造成 CPU 性能浪费。

示例代码:

public class Test {

    public static final int times = 1;
    public static final int mod = 3;
    public static int state = 0;
    public static final Lock lock = new ReentrantLock();

    static class Task implements Runnable {
        private final int num;
        private final String context;
        public Task(int num, String context) {
            this.num = num;
            this.context = context;
        }

        @Override
        public void run() {
            for (int i = 0; i < times;) {
                lock.lock();
                if (state % mod == num) {
                    System.out.println(context);
                    state++;
                    i++;
                }
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        Task one = new Task( 0, "one");
        Task two = new Task(1, "two");
        Task three = new Task(2, "three");
        new Thread(one).start();
        new Thread(two).start();
        new Thread(three).start();
        while (true) {
        }
    }
}

协同型的实现方式,当前线程打印完后通知下一个该打印的线程,使用通知机制理论上会有更高的执行效率。

对于多线程之间的通信,我们可以用 wait / notify,或者 Condition 的 await / signal 方法。

示例代码:

public class Test {

    public static final Object object = new Object();

    static class Task implements Runnable {
        private boolean flag;
        private final Task preTask;
        private final String context;
        public Task(Task preTask, String context) {
            this.preTask = preTask;
            this.context = context;
        }

        @Override
        public void run() {
            synchronized (object) {
                if (preTask != null && !preTask.flag) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(context);
                flag = true;
                object.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        Task one = new Task( null, "one");
        Task two = new Task(one, "two");
        Task three = new Task(two, "three");
        new Thread(one).start();
        new Thread(two).start();
        new Thread(three).start();
        while (true) {
        }
    }
}