1. 程式人生 > >一起聊聊3個執行緒依次列印1、2、3...的故事

一起聊聊3個執行緒依次列印1、2、3...的故事

3個執行緒依次列印1、2、3…這個問題,常常被作為面試題,題目如下:

三個執行緒,一個執行緒負責列印1,4,7,……;第二個負責列印2,5,8,……,第三個負責列印3,6,9,……,要求在控制檯中按順序輸出1,2,3,4,5,6……。

這個題目肯定是要啟動3個執行緒的,那怎麼讓這3個執行緒“協作”按順序列印1、2、3呢?從大的方面來講,這種“協作”可分為以下兩種:

  • 競爭型:每個執行緒都搶著去列印,如果發現不該自己列印,則準備下一輪搶。由於大家都是競爭的,因此需要用鎖機制來保護。
  • 協同型:當前執行緒執行緒列印之後通知下一個執行緒去列印,這種需要確認好第一個執行緒列印時機。由於是協同型的因此可以不用鎖機制來保護,但是需要一個通知機制。

競爭型列印

多個執行緒競爭型列印,優勢是程式碼簡單易懂,劣勢是執行緒爭搶是CPU排程進行的,可能該某個執行緒列印時結果該執行緒遲遲未被CPU排程,結果其他執行緒被CPU排程到但是由於不能執行列印操作而繼續爭搶,造成CPU效能浪費。示例程式碼如下:

public class DemoTask implements Runnable {

    // 這裡將lock物件換成 Lock(ReentrantLock) 進行lock/unlock也是可以的
    private static final Object lock = new Object();
    private static final int MAX = 1024;
    private static int current = 1;

    private int index;
    public DemoTask(int index) {
        this.index = index;
    }

    @Override
    public void run() {
        while (current <= MAX) {
            synchronized (lock) {
                if ((current <= MAX) && (current % 3 == index)) {
                    System.out.println(current++);
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        List<Thread> threadList = Arrays.asList(
                new Thread(new DemoTask(0)),
                new Thread(new DemoTask(1)),
                new Thread(new DemoTask(2))
        );

        threadList.forEach(Thread::start);
        for (Thread thread : threadList) {
            thread.join();
        }
    }
}

協同型列印

多個執行緒協同型列印,優勢是各個執行緒使用“通知”機制進行協同分工,理論上執行效率較高,不過要使用對應的“通知”機制。關於如何“通知”,第一種是可使用Java物件的 wait/notify 或者Conditon物件的 await/signal ,第二種是以事件或者提交任務的方式(比如通過提交“待列印數字”這個任務給下一個執行緒)。

下面以第二種方式進行程式碼分析,比如當前執行緒通過submit給下一個執行緒一個“待列印數字”的任務,這樣很容易想到使用只包含1個執行緒的執行緒池來實現,示例程式碼如下:

public class DemoTask implements Runnable {

    // 主執行緒要等待執行緒列印完畢,使用CountDownLatch通知機制
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    private static List<ExecutorService> threadList = new ArrayList<>();
    private static final int MAX = 1024;
    private static int current = 1;

    @Override
    public void run() {
        if (current <= MAX) {
            System.out.println(current++);
            threadList.get(current % threadList.size()).submit(new DemoTask());
        } else {
            countDownLatch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            threadList.add(Executors.newFixedThreadPool(1));
        }

        threadList.get(0).submit(new DemoTask());
        countDownLatch.await();
        threadList.forEach(ExecutorService::shutdown);
    }
}

推薦閱讀