你應該會的一道多執行緒筆試題

cmazxiaoma發表於2018-06-18

###前言

最近也面了好多家企業,也總結到很多筆試經驗和麵試經驗。筆試大多數Java題目都是牛客網原題和簡單排序,資料庫,Java基礎概念,資料結構,MVC模式等。面試官問的題目涉及的知識無非是Java基礎知識,設計模式,網路等。我發現出現頻率很高的知識點有多執行緒,設計模式(單例模式,策略模式,觀察者模式)等。今天就來說一下筆試和麵試中常見的多執行緒題目。

dream.jpg

###筆試

  • 題目:有ABC三個執行緒,,A執行緒輸出AB執行緒輸出BC執行緒輸出C,要求,同時啟動三個執行緒,,按順序輸出ABC,迴圈10次。這道題目出現的頻率很高啊。

#####第一種思路

  • 建立3個執行緒輪流輸出,用lock物件去同步執行緒的狀態,用count變數標識出哪個執行緒,MAX變數用於邊界控制,適時退出輪詢。(沒有用到wait()和notify()執行緒通訊機制)

  • 手寫程式碼

public class PrintABC {

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        Thread a = new Thread(new PrintfABCThread("A", lock, 0));
        Thread b = new Thread(new PrintfABCThread("B", lock, 1));
        Thread c = new Thread(new PrintfABCThread("C", lock, 2));

        a.start();
        b.start();
        c.start();
    }
}

class PrintfABCThread implements Runnable {
    private String name;
    private Lock lock;
    private Integer flag;

    public static int count = 0;

    public static final int MAX = 30;

    public PrintfABCThread(String name, Lock lock, Integer flag) {
        this.name = name;
        this.lock = lock;
        this.flag = flag;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();

            if (count >= MAX) {
                lock.unlock();
                return;
            }

            if (count % 3 == flag) {
                System.out.println(name);
                count++;
            }
            lock.unlock();
        }
    }
}
複製程式碼
  • 輸出結果
    image.png

#####第二種思路

  • 通過Thread類的join()方法讓我們開啟的執行緒加入到主執行緒,只有我們開啟的新執行緒結束後,主執行緒才能繼續執行。(不滿足題意,建立了30個執行緒,而且沒有同時開啟執行緒)

  • 手寫程式碼

public class PrintfABC {

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread a = new Thread(new PrintThread("A"));
            a.start();
            a.join();
            Thread b = new Thread(new PrintThread("B"));
            b.start();
            b.join();
            Thread c = new Thread(new PrintThread("C"));
            c.start();
            c.join();
        }
    }
}

class PrintThread implements Runnable {
    private String name;

    public PrintThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(name);
    }
}
複製程式碼
  • 輸出結果
    image.png
    #####第三種思路
    • 定義一個MainLock繼承於ReentrantLock,裡面維護著3個condition,用於執行緒之間的通訊。
public class MainLock extends ReentrantLock {

    private static final long serialVersionUID = 7103258623232795241L;

    private int count = 0;

    private final int max;

    private final Condition a;

    private final Condition b;

    private final Condition c;

    public MainLock(int max) {
        this.max = max;
        this.a = this.newCondition();
        this.b = this.newCondition();
        this.c = this.newCondition();
    }

    public boolean isEnd() {
        if (count >= max) {
            return true;
        }

        return false;
    }

    public void increase() {
        count++;
    }

    public int getCount() {
        return this.count;
    }

    public int getMax() {
        return this.max;
    }

    public Condition getA() {
        return this.a;
    }

    public Condition getB() {
        return this.b;
    }

    public Condition getC() {
        return this.c;
    }

    public boolean isA() {
        return count % 3 == 0;
    }

    public boolean isB() {
        return count % 3 == 1;
    }

    public boolean isC() {
        return count % 3 == 2;
    }
}

複製程式碼
  • 建立一個定長執行緒池,開啟3個執行緒,分別去處理輸出A,B,C的請求。
public class Main {

    public static void main(String[] args) {
        MainLock lock = new MainLock(30);
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.submit(new AThread(lock));
        pool.submit(new BThread(lock));
        pool.submit(new CThread(lock));

        pool.shutdown();
    }
}

class AThread implements Runnable {
    private final MainLock lock;

    public AThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isA()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getB().signal();
                } else {
                    try {
                        lock.getA().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.println("A ");
    }
}

class BThread implements Runnable {
    private final MainLock lock;

    public BThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isB()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getC().signal();
                } else {
                    try {
                        lock.getB().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.println("B ");
    }
}

class CThread implements Runnable {
    private final MainLock lock;

    public CThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isC()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getA().signal();
                } else {
                    try {
                        lock.getC().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.println("C ");
    }
}
複製程式碼
  • 輸出結果
    image.png

  • 第二個題目: 用多執行緒去處理"abc""def"“ghi”這個三個字串,讓它們以"adg""beh"“cfi”這種形式輸出。這個題目之前是紅星美凱龍技術部筆試卷的壓軸題,分值是20分。

#####第一種思路 其實跟第一個題目的解決思路是差不多,唯一變的就是我們要獲取下標訪問字串從而獲取字元。我們可以通過count變數來標識由哪一個執行緒輸出,通過count / 3 獲取下標。(還是沒有用到wait()和notify()機制)

public class DemoTwo {

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        Thread a = new Thread(new PrintThread("abc", lock, 0));
        Thread b = new Thread(new PrintThread("def", lock, 1));
        Thread c = new Thread(new PrintThread("ghi", lock, 2));

        a.start();
        b.start();
        c.start();
    }
}

class PrintThread implements Runnable {
    private String name;
    private Lock lock;
    private Integer flag;

    public static int count = 0;

    public static int MAX = 9;

    public PrintThread(String name, Lock lock, Integer flag) {
        this.name = name;
        this.lock = lock;
        this.flag = flag;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();

            if (count >= MAX) {
                lock.unlock();
                return;
            }

            if (count % 3 == flag) {
                System.out.print(name.charAt(count / 3) + " ");
                count++;
            }

            lock.unlock();
        }
    }
}
複製程式碼
  • 輸出結果。
    image.png

#####第二種思路

  • 和上面的思路是一樣的。(沒有同時開啟3個執行緒)

  • 手寫程式碼

public class DemoOne {

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            Thread a = new Thread(new MyThread("abc", i));
            a.start();
            a.join();

            Thread b = new Thread(new MyThread("def", i));
            b.start();
            b.join();

            Thread c = new Thread(new MyThread("ghi", i));
            c.start();
            c.join();

            System.out.println("");
        }
    }
}

class MyThread implements Runnable {
    private String str;
    private int index;

    public MyThread(String str, int index) {
        this.str = str;
        this.index = index;
    }

    @Override
    public void run() {
        System.out.print(String.valueOf(str.charAt(index)) + " ");
    }
}
複製程式碼
  • 輸出結果。
    image.png

#####第三種思路

public class Main3 {

    public static void main(String args[]) {
        MainLock lock = new MainLock(9);
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.submit(new XThread(lock));
        pool.submit(new YThread(lock));
        pool.submit(new ZThread(lock));

        pool.shutdown();
    }
}

class XThread implements Runnable {
    private final MainLock lock;

    public XThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isA()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getB().signal();
                } else {
                    try {
                        lock.getA().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.print("abc".charAt(lock.getCount() / 3));
    }

}

class YThread implements Runnable {
    private final MainLock lock;

    public YThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isB()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getC().signal();
                } else {
                    try {
                        lock.getB().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.print("def".charAt(lock.getCount() / 3));
    }

}

class ZThread implements Runnable {
    private final MainLock lock;

    public ZThread(MainLock lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (lock.isC()) {
                    if (lock.isEnd()) {
                        System.exit(1);
                    } else {
                        print();
                    }
                    lock.increase();
                    lock.getA().signal();
                } else {
                    try {
                        lock.getC().await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }

    private void print() {
        System.out.print("ghi".charAt(lock.getCount() / 3));
    }

}
複製程式碼
  • 輸出結果
    image.png

###面試 昨天去掃唄面試,面試官問我多執行緒的實現的二種方式和彼此之間的區別。這個也很簡單,百度也爛大街了。

  • 採用extends Thread 方式

    • 優點:程式設計簡單,如果要訪問當前執行緒,無需使用Thread.currentThread()方法,可以直接用this,即可獲取當前執行緒。

    • 缺點:由於繼承了Thread,類無法再繼承其他的父類。

    • 使用方式:直接new 相應的執行緒類即可。

  • 採用implements Runnable 方式

    • 優點:沒有繼承Thread類,所以可以繼承其他的父類,在這種形式下,多個執行緒可以共享同一個物件,所以非常合適多個相同的執行緒來處理同一份資源的情況下,把cpu程式碼和資料分開,形成清晰的模型,較好的體現了物件導向的思想。適用場景,比如賣票。

    • 缺點:程式設計稍微複雜,如果要訪問當前執行緒,必須使用Thread.currentThread()方法。

    • 使用方式:不能直接建立所需類的物件並執行它,而是必須從Thread類的一個例項內部啟動它。

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
複製程式碼

###尾言

就算失望不能絕望,明天又去面試,美滋滋。

相關文章