一起聊聊3個執行緒依次列印1、2、3…的故事
- 2020 年 3 月 10 日
- 筆記

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); } }