記一道阿里多線程筆試題

最近有同事收到阿里的面試邀請,因地點在杭州就先進行了一輪線上筆試,其中有這樣一道題,和大家分享一下。

題目是這樣的:用多個線程交替打印字符,如字符串"ali",一個線程打印a,一個線程打印l,一個線程打印i , 一個線程打印空格,如:ali ali ali …
代碼支持拓展,能打印任意字符串"alibaba",如:alibaba alibaba alibaba alibaba …

是一道經典的線程交替打印相關的題目,和網上輪流打印ABC的題目很相似,先說一下輪流打印ABC的解題思路,再回過頭來看阿里的這道題。

關鍵點主要有兩個,一是如何讓對應的線程打印對應的字符,一是如何控制打印的順序。
一般的解決思路是用可重入鎖 ReentrantLock 加 Condition,代碼如下:

public class AlternativePrint {
    private ReentrantLock lock = new ReentrantLock();
    private Condition conditionA = lock.newCondition();
    private Condition conditionB = lock.newCondition();
    private Condition conditionC = lock.newCondition();
	// 控制打印順序
    private int number = 1;

    public void loopA(){
        lock.lock();
        try {
        	// 未到打印時機,等待
            if (number != 1){
                conditionA.await();
            }
           
            System.out.print(Thread.currentThread().getName());
		
            number = 2;
            // 喚醒下一個打印線程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void loopB(){
        lock.lock();
        try {
            if (number != 2){
                conditionB.await();
            }

            System.out.print(Thread.currentThread().getName());

            number = 3;
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void loopC(){
        lock.lock();
        try {
            if (number != 3){
                conditionC.await();
            }

            System.out.print(Thread.currentThread().getName());

            number = 1;
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

測試類:

public static void main(String[] args) {

    AlternativePrint alternativePrint = new AlternativePrint();

    new Thread("A"){
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                alternativePrint.loopA();
            }
        }
    }.start();

    new Thread("B"){
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                alternativePrint.loopB();
            }
        }
    }.start();

    new Thread("C"){
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                alternativePrint.loopC();
            }
        }
    }.start();

}

相信這道題難不倒大家,那回頭再來看阿里這道題。這道題不僅需要解決上面兩個問題,還需要用字符長度的線程去打印,我的解決思路是這樣的:

  • 以字符串長度創建線程,再加一個打印空格的線程
  • 將線程和打印的字符封裝成一個對象
  • 爲了控制打印的順序,將對象之間的關係維護成單向循環鏈表

代碼如下:

public class Print {

    private ReentrantLock lock;

    private String str;

    private int printTimes;

    private void print() {
        List<Item> list = new ArrayList<>();
        for (int i = 0; i < str.length(); i++) {
            list.add(new Item(str.charAt(i), printTimes, lock));
        }
        list.add(new Item((char) 32, printTimes, lock));

        for (int i = 0; i < list.size(); i++) {
            Item item = list.get(i);
            if (i == list.size() - 1) {
                item.setNext(list.get(0));
            }
            else {
                item.setNext(list.get(i + 1));
                if (i == 0) {
                    // 激活第一個線程
                    item.setActive(true);
                }
            }
        }

        list.forEach(Thread::start);
    }

    public Print(String str, int printTimes) {
        this.str = str;
        this.printTimes = printTimes;
        this.lock = new ReentrantLock();
    }

    static class Item extends Thread {

        private char ch;

        private int printTimes;

        private ReentrantLock lock;

        private Condition condition;

        private Item next;

        private boolean active;

        @Override
        public void run() {
            for (int i = 0; i < printTimes; i++) {
                lock.lock();
                try {
                    if (!active) {
                        condition.await();
                    }

                    System.out.print(ch);
                    active = false;

                    if (!next.isActive()) {
                        next.setActive(true);
                        next.getCondition().signal();
                    }
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
                finally {
                    lock.unlock();
                }
            }
        }

        public Item(char ch, int printTimes, ReentrantLock lock) {
            this.ch = ch;
            this.printTimes = printTimes;
            this.lock = lock;
            this.condition = lock.newCondition();
        }

        public Condition getCondition() { return condition; }

        public Item getNext() { return next; }

        public void setNext(Item next) { this.next = next; }

        public void setActive(boolean active) { this.active = active; }

        public boolean isActive() { return active; }

    }

}

測試類:

public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    System.out.print("請輸入字符串:");
    String next = input.next();
    System.out.print("請輸入打印次數:");
    int printTimes = input.nextInt();

    Print print = new Print(next, printTimes);
    print.print();
}

打印結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章