面試被問AQS、ReentrantLock答不出來?這些知識點讓我和麪試官聊了半小時!

據說微信搜索《Java魚仔》會變動強哦!java

本文收錄於JavaStarter ,裏面有我完整的Java系列文章,學習或面試均可以看看哦git

併發編程算是Java的一個難點,常常作業務相關的程序員基本上用不到juc的包,可是這些知識點十分重要,因此無論在哪裏,時刻保持學習真的很重要。程序員

(一)AQS概述

Java併發編程的核心在於java.concurrent.util包,juc中大多數同步器的實現都圍繞了一個公共的行爲,好比等待隊列、條件隊列、獨佔獲取、共享獲取等,這個行爲的抽象就是基於AbstractQueuedSynchronized(AQS)。AQS定義了多線程訪問共享資源的同步器框架。github

簡單來說,AQS就比如一個行爲準則,而併發包中的大多數同步器在這個準則下實現。面試

AQS具有如下的幾個特性:阻塞等待隊列、共享/獨佔、公平/非公平、可重入、容許中斷。編程

若是你點開JUC發源碼,會發現大量同步器的實現,好比:Lock、Latch、Barrier等都基於AQS實現。微信

(二)幾個重要的知識點

在AQS中,咱們須要記住幾個重要的知識點:多線程

在這裏插入圖片描述

一、AQS的實現一般是定義內部類Sync繼承AQS,將同步器的全部調用都映射到Sync對應的方法上。併發

二、AQS內部有個屬性叫state,表示資源的可用狀態。state有三種訪問方式getState()、setState()、compareAndSetState()框架

三、AQS定義了兩種資源的共享方式:獨佔(Exclusive)如ReentrantLock、共享(Share)如Semaphore或CountDownLatch

四、AQS中定義了同步等待隊列,用於存放等待線程的一個隊列。

這幾個知識點會在後面的內容中使用到。

(三)ReentrantLock

咱們經過ReentrantLock這個示例來更深刻的瞭解AQS。我會經過上面四個知識點去講解ReentrantLock中AQS的使用。

一、首先進入ReentrantLock的源碼內部,直接就能看到ReentrantLock中定義的內部類Sync

在這裏插入圖片描述

Sync繼承了AQS,按AQS去指定同步規則。

二、既然繼承了AQS,ReentrantLock內部也至關於有了state,這個state用來記錄上鎖的次數,ReentrantLock是個可重入鎖,若是屢次上鎖,state會記錄上鎖的次數,須要釋放一樣次數的鎖纔算把鎖釋放完。

三、ReentrantLock的資源是獨佔的,AbstractQueuedSynchronized繼承了一個叫AbstractOwnableSynchronizer的抽象類:

在這裏插入圖片描述

在這個類中,有個變量叫exclusiveOwnerThread,這個變量記錄着當前是哪一個線程獨佔了鎖。

四、同步等待隊列:因爲ReentrantLock是個獨佔的鎖,當有一個線程在使用這個鎖的時候,其餘線程就要到隊列中去等待,這個隊列是一種基於雙向鏈表的隊列(類CLH隊列),節點中存放線程信息。

在這裏插入圖片描述

(四)可重入鎖

在介紹AQS時,咱們講到了AQS中有個狀態值state,這個值用來判斷當前資源的可用狀態。可重入鎖的意思就是對一個對象能夠實現屢次加鎖,state就用來記錄加鎖的次數。下面寫一段代碼:

public class ReentrantLockTest {
    //定義全局的鎖對象
    private static final Lock lock=new ReentrantLock(true);
    public static int count=0;
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                testlock();
            }
        },"線程A").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                testlock();
            }
        },"線程B").start();
    }

    private static void testlock() {
        lock.lock();
        count++;
        System.out.println(Thread.currentThread().getName()+"第一次加鎖"+count);
        lock.lock();
        count++;
        System.out.println(Thread.currentThread().getName()+"第二次加鎖"+count);
        count--;
        lock.unlock();
        System.out.println(Thread.currentThread().getName()+"第一次解鎖"+count);
        count--;
        lock.unlock();
        System.out.println(Thread.currentThread().getName()+"第二次解鎖"+count);
    }
}

生成兩個線程,讓他們去執行testlock方法,而後在testlock方法的開始和結束加鎖,保證同時只有一個線程能夠執行裏面的方法。最後的結果是線程有序執行:

在這裏插入圖片描述

在代碼中,咱們進行了兩次lock,這就是可重入鎖。咱們經過斷點調試,來分析第二次加鎖後lock中的值,下面給出了說明。

在這裏插入圖片描述

(五)公平鎖與非公平鎖

咱們在用構造方法建立ReentrantLock的時候,能夠傳入一個boolean類型的參數,true或false

private static final Lock lock=new ReentrantLock(true);

這裏的true和false表明了建立的ReentrantLock對象是公平鎖仍是非公平鎖

在這裏插入圖片描述

經過上文的學習,咱們知道當有線程在使用鎖的時候,其餘線程是處於等待隊列中的,而一旦鎖被釋放後,他會去喚醒等待隊列中的第一個鎖

若是是公平鎖,當有新的線程來的時候,他會先去看看等待隊列中有沒有等待的線程,若是有,則乖乖跑到最後去排隊

若是是非公平鎖,當有新的線程來的時候,直接看state的狀態,若是發現是0,無論等待隊列有沒有等待的線程,直接去和被喚醒的鎖競爭,若是競爭失敗了,則乖乖跑到隊列最後去排隊,不然就直接佔有鎖。

相關文章
相關標籤/搜索