你應該會的一道多線程筆試題

###前言面試

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

dream.jpg

###筆試編程

  • 題目:有ABC三個線程,,A線程輸出AB線程輸出BC線程輸出C,要求,同時啓動三個線程,,按順序輸出ABC,循環10次。這道題目出現的頻率很高啊。

#####第一種思路設計模式

  • 建立3個線程輪流輸出,用lock對象去同步線程的狀態,用count變量標識出哪一個線程,MAX變量用於邊界控制,適時退出輪詢。(沒有用到wait()和notify()線程通訊機制)bash

  • 手寫代碼網絡

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個線程,並且沒有同時開啓線程)多線程

  • 手寫代碼ide

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()機制)ui

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);
    }
複製代碼

###尾言

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

相關文章
相關標籤/搜索