有個小夥伴最近諮詢我,前段時間他被面試官問了synchronized是公平鎖仍是非公平鎖?當時就蒙圈了,最後面試結果可想而知,今天咱們就用一個通俗的案例加上代碼來講明公平鎖和非公平鎖。其實公平鎖這個概念是JUC工具包纔有的,好比ReentrantLock纔有公平鎖的概念,這篇文章咱們結合生活中的實例用2段代碼說明ReentrantLock公平鎖和非公平鎖,以及證實synchronized是非公平鎖的。但願對小夥伴有幫助。
java
/** * @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公平鎖和非公平鎖。
c++
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另外一種做爲公平鎖的狀況。
面試
基於上面的案例,咱們不重複貼代碼了,將上述代碼中13行的private static final Lock LOCK = new ReentrantLock(false);參數由false改成true,private static final Lock LOCK = new ReentrantLock(true);不管執行多少次能夠得出一個結論:先排隊的童鞋能先打飯,不容許插對體現的就是公平鎖。
多線程
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++實現。
工具
今天用生活中的例子轉化成代碼,詳細的介紹了公平鎖和非公平鎖,並簡單的介紹了aqs實現原理,給您的建議是認真把代碼敲一遍,若是執行了一遍代碼應該能看明白,喜歡的請點贊加關注哦。我是叫練【公衆號】,邊叫邊練。
測試