Java面試題:多個線程交替執行

Java面試題:多個線程交替執行

前言

最近在一些技術羣裏看到有很多小夥伴面試的時候碰到這個多線程的筆試題,實現兩個線程交替打印,或者是實現多個線程的交替打印這種類似的題目。

本文提供三種解題思路和實現

  • 利用 Condition 類實現
  • 利用公平鎖實現
  • 超過 2 個線程,利用隊列來保證線程執行的順序

項目環境

1.Condition 版本

  • 缺陷是超過 2 個線程之後無法保證執行的順序
public class ThreadAlternateConditionDemo {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();// 默認非公平
        Condition condition = lock.newCondition();
        ConditionThread conditionThread = new ConditionThread(lock, condition, 100);
        new Thread(conditionThread, "Thread-A").start();
        new Thread(conditionThread, "Thread-B").start();
        new Thread(conditionThread, "Thread-C").start();
        new Thread(conditionThread, "Thread-D").start();
    }

    static class ConditionThread implements Runnable {

        Lock lock;

        Condition condition;

        int count;

        public ConditionThread(Lock lock, Condition condition, int count) {
            this.lock = lock;
            this.condition = condition;
            this.count = count;
        }

        @Override
        public void run() {
            for (int i = 0; i < count; i++) {
                try {
                    lock.lock();
                    condition.signal();
                    System.out.println(Thread.currentThread().getName() + "-> 執行");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

}

執行結果:
在這裏插入圖片描述

2.公平鎖

  • 這裏使用 ReentrantLock 中的公平鎖
  • 缺陷是超過 2 個線程之後無法保證執行的順序
public class ThreadAlternateFairSyncLockDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock(true);// 公平
        Task task = new Task(lock, 10);
        new Thread(task, "Thread-A").start();
        new Thread(task, "Thread-B").start();
        new Thread(task, "Thread-C").start();
    }

    static class Task implements Runnable {

        Lock lock;

        int count;

        public Task(Lock lock, int count) {
            this.lock = lock;
            this.count = count;
        }

        @Override
        public void run() {
            for (int i = 0; i < count; i++) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "-> 執行" + i);
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

執行結果:
在這裏插入圖片描述

3.如何解決線程打印順序的問題

先來討論一下在第一個 Conidtion 的例子中這個亂序問題如何產生的?

相關代碼如下:

                try {
                    lock.lock();
                    condition.signal();
                    System.out.println(Thread.currentThread().getName() + "-> 執行");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

假設線程A執行完打印,之後condition.await() 掛起線程A,最後 lock.unlock() 釋放鎖,此時有線程B、線程C、線程D三個線程來搶佔鎖,無法保證線程B一定搶佔到鎖,所以會產生亂序打印的效果,但是如果只有 A、B 兩個線程,那麼就不會有這個問題。

解題思路如下:

  • 預先設置一個隊列,將 A、B、C按順序存放在隊列中。

  • 每次執行,先從隊列中取一個值,判斷這個值是否和當前線程的標識相等,如果相等就執行打印,如果不相等釋放鎖,讓其他線程繼續搶佔鎖。

實現代碼如下:

  • 隊列採用的 LinkedBlockingQueue 無界隊列,也可以用其他的隊列(不一定是阻塞隊列)
  • 循環退出條件 queue.peek() != null 隊列沒有元素
  • queue.peek() 返回隊列的頭元素但並不刪除
  • 線程執行打印之後,通過 queue.poll() 刪除已經執行的隊列元素
  • atomicInteger 只是爲了打印循環的次數
public class ThreadAlternateConditionOrderDemo {

    private static LinkedBlockingQueue<String> queue = new LinkedBlockingQueue();

    static {
        try {
            for (int i = 0; i < 100; i++) {
                queue.put("A");
                queue.put("B");
                queue.put("C");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        Lock lock = new ReentrantLock();// 默認非公平
        Condition condition = lock.newCondition();
        ConditionThread conditionThreadA = new ConditionThread(lock, condition, "A");
        ConditionThread conditionThreadB = new ConditionThread(lock, condition, "B");
        ConditionThread conditionThreadC = new ConditionThread(lock, condition, "C");
        new Thread(conditionThreadA, "Thread-A").start();
        new Thread(conditionThreadB, "Thread-B").start();
        new Thread(conditionThreadC, "Thread-C").start();

    }

    static class ConditionThread implements Runnable {

        Lock lock;

        Condition condition;

        String threadFlag;

        public ConditionThread(Lock lock, Condition condition, String threadFlag) {
            this.lock = lock;
            this.condition = condition;
            this.threadFlag = threadFlag;
        }

        @Override
        public void run() {
            while (queue.peek() != null) {
                try {
                    lock.lock();
                    condition.signal();
                    String element = queue.peek();// 拿到阻塞隊列頭部的元素的 flag
                    if (null != element && element.equals(threadFlag)) {// 如果和當前線程的 flag 相同,則執行線程
                        queue.poll();// 刪除這個頭部的元素
                        System.out.println(Thread.currentThread().getName() + "-> 執行");
                        atomicInteger.incrementAndGet();
                        System.out.println(atomicInteger.get());
                        condition.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

}

執行結果:
在這裏插入圖片描述
可以看到執行到 300 次,順序依然是 A -> B-> C。

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