公平鎖 == 絕對有序?淺談對Java公平鎖使用的誤區

踩坑源於一道經典的多線程面試題: 用兩個線程交替打印1~100的奇偶數html

乍一看想解決方案仍是很多的,好比暴力法直接判斷線程名, 直接判斷奇偶等,或者用通知等待方案。可是有沒有一種簡單有效的實現方法?設想了一下,利用公平鎖的話,應該就很簡單了,思路以下:java

AB兩個線程一塊兒競爭公平鎖,A若是先得到了鎖, 那麼B進行等待,A執行完成後,B的等待時間是最長的,那麼B必定會獲取本次的鎖,依次類推,實現交替打印。面試

如何使用公平鎖也是很簡單,juc包下的ReentrantLock已經給咱們留好了接口api

/**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
複製代碼

就像上面源碼所示,咱們只須要在構造函數傳入true,就會使用公平同步,構造函數的註釋和咱們設想的是一致的,if this lock should use a fair ordering policy 則傳true就會採用公平排序策略。bash

看起來沒啥毛病,動手寫了一下實現:多線程

private static final int COUNT = 100;

    private static int start = 1;

    static ReentrantLock lock = new ReentrantLock(true);

    public static void main(String[] args) {
        Runnable task = () -> {
            for (; ; ) {
                try {
                    lock.lock();
                    if (start <= COUNT) {
                        System.out.println(Thread.currentThread().getName() + "=> " + start++);
                    } else {
                        System.exit(0);
                    }
                } finally {
                    lock.unlock();
                }
            }
        };
        new Thread(task).start();
        new Thread(task).start();
    }
複製代碼

代碼看起來是比暴力法精簡多了,輸出一下,檢證一下結果。 打印了五遍左右,大概沒什麼問題,可是第六次打印的結果,發現了一個細節,是有極小極小的幾個狀況,沒有按照預期進行打印,以下:oracle

pool-1-thread-1=> 1
pool-1-thread-2=> 2
pool-1-thread-1=> 3
pool-1-thread-2=> 4
......
pool-1-thread-1=> 55
pool-1-thread-2=> 56
pool-1-thread-1=> 57
pool-1-thread-2=> 58   
pool-1-thread-2=> 59  ←※Error Print※
pool-1-thread-1=> 60
複製代碼

59這個數字按理說是thread-1去打印,可是thread-2卻連續打了兩條,這個狀況就很讓人困惑了,怕是用了假的公平鎖吧? 又連續測試了幾回,發現的確是有偶發這個狀況,則去查閱了一下相關資料,一探究竟。 先看源碼,公平鎖比非公平鎖的代碼中,主要是多了一個判斷條件,就是public final boolean hasQueuedPredecessors()大概就是維護了一個隊列,若是有線程在排隊了,則此次是輪不到你這個沒排隊的。沒什麼問題,繼續找,發現一段了doc中關於公平鎖的這樣的描述: > Note however, that fairness of locks does not guarantee fairness of thread scheduling. Thus, one of many threads using a fair lock may obtain it multiple times in succession while other active threads are not progressing and not currently holding the lock.函數

docs.oracle.com/javase/8/do… 翻譯過來則是: 可是請注意,鎖的公平性不能保證線程調度的公平性。 所以,使用公平鎖的許多線程之一可能會連續屢次得到它,而其餘活動線程沒有進行且當前未持有該鎖。測試

看到這裏疑問就解開了,綜合官方的介紹,我的理解爲:公平鎖會盡量的保證鎖的公平,可是最後Thread scheduling(線程調度)是由JVM和操做系統去決定的,若是業務上須要有強順序性的話,不能依靠公平鎖。若是有大佬有詳細的看法,能夠在評論區告訴我。this

分割線------------------------------------------------------------------------------

到這裏其實還有一個小小的疑問,就是咱們的打印結果,大多數都是正確的,不正確的結果,也只是有不多的數字是錯誤的線程打印的。看起來公平鎖沒有理論上的那麼不靠譜,大膽的推測了一下爲何OS&JVM很是偶然的出現非預期的結果: (如下爲本人推測) 目前線程中鎖的代碼塊中,處理只有輸出數字這一個操做,處理時間極短,能夠忽略不計。這樣可能就致使了在某些極端的一個時刻,A線程執行完後,沒有及時的去獲取鎖,線程B就瓜熟蒂落的獲取了兩次鎖。 驗證一下本身猜測,將只有打印的代碼塊,增長了睡眠10ms的操做。

System.out.println(Thread.currentThread().getName() + "=> " + start++);
Thread.sleep(10);
複製代碼

輸出了屢次進行驗證,竟然和預期結果是正確的。 到這裏推測,個人猜想有多是正確的,可是既然官方文檔已經寫的很清晰了,仍是不要這麼去使用的好。

相關文章
相關標籤/搜索