排隊打飯:公平鎖和非公平鎖(面試)

簡介


有個小夥伴最近諮詢我,前段時間他被面試官問了synchronized公平鎖仍是非公平鎖?當時就蒙圈了,最後面試結果可想而知,今天咱們就用一個通俗的案例加上代碼來講明公平鎖非公平鎖。其實公平鎖這個概念是JUC工具包纔有的,好比ReentrantLock纔有公平鎖的概念,這篇文章咱們結合生活中的實例用2段代碼說明ReentrantLock公平鎖和非公平鎖,以及證實synchronized是非公平鎖的。但願對小夥伴有幫助。
java

公平鎖、非公平鎖概念


  • 公平鎖:舉一個簡單例子,有五個同窗天天必須排隊去打飯,爲了簡單起見,咱們給這五名同窗沒人定義一個編號,分別爲編號001編號005,這五名同窗按先來後到的排隊,打飯,先來的同窗能先打到飯。每一個同窗都是一個線程,在這個過程當中後來的同窗是不容許插隊的,這就是公平鎖。
  • 非公平鎖:後來到同窗不必定後打到飯,在打飯的過程當中,是容許插隊的,這種線程插入的行爲人們認爲是不公平的。舉個例子,好比編號爲001,002,003,004的同窗先到先排隊了,005最後來排隊本應該排在004後面的,可是005看001正好打完飯離開,他就去插隊了,也就是打飯的順序由001->002->003->004->005變爲001->005->002->003->004。其實你如今應該理解了,公平鎖就是正常排隊,非公平就是插隊。固然你可能會有疑問?是否是005插到001的後面必定會成功,答案是不必定,這要看時機的,咱們剛纔說了「005看001正好打完飯離開」,下面應該是002了,可能打飯阿姨還沒問002準備吃什麼,就看005已經排到前面去了,那005就插隊成功了,這就是時機。下面咱們用程序代碼來加深理解。

synchronized非公平鎖


/**
 * @author :jiaolian
 * @date :Created in 2020-12-31 16:01
 * @description:食堂打飯:synchronized不公平
 * @modified By:
 * 公衆號:叫練
 */
public class SyncUnFairLockTest {

    //食堂
    private static class DiningRoom {
        //獲取食物
        public void getFood() {
            System.out.println(Thread.currentThread().getName()+":排隊中");
            synchronized (this) {
                System.out.println(Thread.currentThread().getName()+":@@@@@@打飯中@@@@@@@");
            }
        }
    }

    public static void main(String[] args) {
        DiningRoom diningRoom = new DiningRoom();
        //讓5個同窗去打飯
        for (int i=0; i<5; i++) {
            new Thread(()->{
                diningRoom.getFood();
            },"同窗編號:00"+(i+1)).start();
        }
    }
}

如上代碼:咱們定義一個內部類DiningRoom表示食堂,getFood方法裏面用synchronized鎖修飾this指向DiningRoom的實例對象(22行中的diningRoom對象),主類中讓編號001至005五個同窗同時去打飯,用於測試先排隊的同窗是否能先打到飯?運行程序獲得其中一種執行結果以下圖所示,002->004->001->003->005同窗先去排隊,但打飯的順序是002->003->001->004->005,說明這裏003和001兩個同窗插隊了,插到004前面了,咱們詳細分析執行過程,002先搶到鎖打飯了,釋放了鎖,原本應該是接下來是004搶到鎖去打飯(由於004是比003先來排隊),但003搶到鎖,打飯了,釋放了鎖,這是第一次插隊。如今仍是來004搶鎖,可是沒搶到又被001搶到了,釋放鎖後才被004搶到,這是第二次插隊,後面分別再是004->005搶到鎖,釋放鎖,程序執行完畢。由於003和001插隊,咱們用代碼證實了synchronized是非公平鎖。緊接着咱們來看下ReentrantLock公平鎖和非公平鎖。
image.png
c++

ReentrantLock非公平鎖


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author :jiaolian
 * @date :Created in 2020-12-31 11:11
 * @description:非公平鎖測試  在獲取鎖的時候和再獲取鎖的順序不一致;
 * @modified By:
 * 公衆號:叫練
 */
public class UnFairLockTest {

    private static final Lock LOCK = new ReentrantLock(false);

    //食堂
    private static class DiningRoom {
        //獲取食物
        public void getFood() {
            try {
                System.out.println(Thread.currentThread().getName()+":正在排隊");
                LOCK.lock();
                System.out.println(Thread.currentThread().getName()+":@@@@@@打飯中@@@@@@@");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                LOCK.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        DiningRoom diningRoom = new DiningRoom();
        //讓5個同窗去打飯
        for (int i=0; i<5; i++) {
            new Thread(()->{
                diningRoom.getFood();
            },"同窗編號:00"+(i+1)).start();
        }
    }
}

如上代碼:咱們在代碼第13行中定義了Lock LOCK = new ReentrantLock(false);ReentrantLock的參數是false表示非公平鎖,上面代碼須要用LOCK.lock()加鎖,LOCK.unlock()解鎖,須要放入try,finally代碼塊中,目的是若是try中加鎖後代碼發生異常鎖最終執行LOCK.unlock(),鎖總能被釋放。主類中讓編號001至005五個同窗同時去打飯,獲得其中一種執行結果以下圖所示,001->004->005->003->002同窗先去排隊,但打飯的順序是001->005->004->003->002,這裏005同窗插隊了,插到004前面。咱們詳細分析執行過程:001先來搶到鎖打飯了並釋放了鎖,接下來本應該是004搶到鎖,由於它先排隊,但005卻在004以前搶到鎖,打飯了,005比004後來,卻先打飯,這就是不公平鎖,後面的執行結果按先來後到執行,程序結束。咱們用代碼證實了ReentrantLock是非公平的鎖。緊接着咱們來看下ReentrantLock另外一種做爲公平鎖的狀況。
image.png
面試

ReentrantLock公平鎖


基於上面的案例,咱們不重複貼代碼了,將上述代碼中13行的private static final Lock LOCK = new ReentrantLock(false);參數由false改成true,private static final Lock LOCK = new ReentrantLock(true);不管執行多少次能夠得出一個結論:先排隊的童鞋能先打飯,不容許插對體現的就是公平鎖。
image.png
多線程

ReentrantLock底層原理


ReentrantLock是基於AbstractQueuedSynchronizer(抽象隊列同步器,簡稱aqs)實現的,aqs底層維護了一個帶頭的雙向鏈表,用來同步線程,鏈表每一個節點用Node表示,每一個Node會記錄線程信息,上下節點,節點狀態等信息,aqs控制Node的生命週期。以下圖所示,aqs也包含條件隊列,鎖和條件隊列(condition)是一對多的關係,也就是說一個鎖能夠對應多個條件隊列,線程間的通訊在條件隊列裏經過await,single/singleAll方法控制,synchronized只有一個條件隊列用wait,notify/notifyAll來實現,這裏不展開說了,《母雞下蛋實例:多線程通訊生產者和消費者wait/notify和condition/await/signal條件隊列》和《Synchronized用法原理和鎖優化升級過程(面試)》能夠看我文章,裏面有大量清晰簡單案例。條件隊列也是以鏈表形式存在。Lock是基於juc包實現,synchronized是本地方法基於c++實現。
image.png
工具

總結


今天用生活中的例子轉化成代碼,詳細的介紹了公平鎖和非公平鎖,並簡單的介紹了aqs實現原理,給您的建議是認真把代碼敲一遍,若是執行了一遍代碼應該能看明白,喜歡的請點贊加關注哦。我是叫練【公衆號】,邊叫邊練。
image.png
測試

相關文章
相關標籤/搜索