欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Java?ReentrantLock鎖的原理與使用

 更新時間:2023年08月29日 08:31:23   作者:M`sakura~  
這篇文章主要為大家詳細介紹了Java中ReentrantLock鎖的原理與使用,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以了解下

一.  AQS內(nèi)部結構介紹

JUC是Java中一個包   java.util.concurrent 。在這個包下,基本存放了Java中一些有關并發(fā)的類,包括并發(fā)工具,并發(fā)集合,鎖等。

AQS(抽象隊列同步器)是JUC下的一個基礎類,大多數(shù)的并發(fā)工具都是基于AQS實現(xiàn)的。

AQS本質并沒有實現(xiàn)太多的業(yè)務功能,只是對外提供了三點核心內(nèi)容,來幫助實現(xiàn)其他的并發(fā)內(nèi)容。

三點核心內(nèi)容:

int state

  • 比如ReentrantLock或者ReentrantReadWriteLock, 它們獲取鎖的方式,都是對state變量做修改實現(xiàn)的。
  • 比如CountDownLatch基于state作為計數(shù)器,同樣的Semaphore也是用state記錄資源個數(shù)。

Node對象組成的雙向鏈表(AQS中)

比如ReentrantLock,有一個線程沒有拿到鎖資源,當線程需要等待,則需要將線程封裝為Node對象,將Node添加到雙向鏈表,將線程掛起,等待即可。

Node對象組成的單向鏈表(AQS中的ConditionObject類中)

比如ReentrantLock,一個線程持有鎖資源時,執(zhí)行了await方法(類比synchronized鎖執(zhí)行對象的wait方法),此時這個線程需要封裝為Node對象,并添加到單向鏈表。

二.  Lock鎖和AQS關系

ReentrantLock就是基于AQS實現(xiàn)的。ReentrantLock類中維護這個一個內(nèi)部抽象類Sync,他繼承了AQS類。ReentrantLock的lock和unlock方法就是調用的Sync的方法。

AQS流程(簡述)

1. 當new了一個ReentrantLock時,AQS默認state值為0, head 和 tail 都為null;

2. A線程執(zhí)行l(wèi)ock方法,獲取鎖資源。

3. A線程將state通過cas操作從0改為1,代表獲取鎖資源成功。

4. B線程要獲取鎖資源時,鎖資源被A線程持有。

5. B線程獲取鎖資源失敗,需要添加到雙向鏈表中排隊。

6. 掛起B(yǎng)線程,等待A線程釋放鎖資源,再喚醒掛起的B線程。

7. A線程釋放鎖資源,將state從1改為0,再喚醒head.next節(jié)點。

8. B線程就可以重新嘗試獲取鎖資源。

注: 修改AQS雙向鏈表時要保證一個私有屬性變化和兩個共有屬性變化,只需要讓tail變化保證原子性即可。不能先改tail(會破壞雙向鏈表)

三.  AQS - Lock鎖的tryAcquire方法

ReentrantLock中的lock方法實際是執(zhí)行的Sync的lock方法。

Sync是一個抽象類,繼承了AQS

Sync有兩個子類實現(xiàn):

  • FairSync: 公平鎖
  • NonFairSync: 非公平鎖

Sync的lock方法實現(xiàn):

//  非公平鎖
final void lock() {
    //  CAS操作,嘗試將state從0改為1
    //  成功就拿到鎖資源, 失敗執(zhí)行acquire方法
    if (compareAndSetState(0, 1))
     // 成功就設置互斥鎖的為當前線程擁有
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
//  公平鎖
final void lock() {
    acquire(1);
}

如果CAS操作沒有成功,需要執(zhí)行acquire方法走后續(xù)

acquire方法是AQS提供的,公平和非公平都是走的這個方法

public final void acquire(int arg) {
    //  1. tryAcquire方法: 再次嘗試拿鎖
    //  2. addWaiter方法: 沒有獲取到鎖資源,去排隊
    //  3. acquireQueued方法:掛起線程和后續(xù)被喚醒繼續(xù)獲取鎖資源的邏輯
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
     // 如果這個過程中出現(xiàn)中斷,在整個過程結束后再自我中斷 
        selfInterrupt();
}

在AQS中tryAcquire是沒有具體實現(xiàn)邏輯的,AQS直接在tryAcquire方法中拋出異常

在公平鎖和非公平鎖中有自己的實現(xiàn)。

非公平鎖tryAcquire方法

//  非公平鎖
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
//  非公平鎖再次嘗試拿鎖 (注:該方法屬于Sync類中)
final boolean nonfairTryAcquire(int acquires) {
    //  獲取當前線程對象
    final Thread current = Thread.currentThread();
    //  獲取state狀態(tài)
    int c = getState();
    //  state是不是沒有線程持有鎖資源,可以嘗試獲取鎖
    if (c == 0) {
        //  再次CAS操作嘗試修改state狀態(tài)從0改為1
        if (compareAndSetState(0, acquires)) {
            //  成功就設置互斥鎖的為當前線程擁有
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //  鎖資源是否被當前線程所持有 (可重入鎖)
    else if (current == getExclusiveOwnerThread()) {
        //  持有鎖資源為當前, 則對state + 1
        int nextc = c + acquires;
        //  健壯性判斷
        if (nextc < 0) // overflow
            //  超過最大鎖重入次數(shù)會拋異常(幾率很小,理論上存在)
            throw new Error("Maximum lock count exceeded");
        //  設置state狀態(tài),代表鎖重入成功
        setState(nextc);
        return true;
    }
    return false;
}

公平鎖tryAcquire方法

//  公平鎖
protected final boolean tryAcquire(int acquires) {
    //  獲取當前線程對象
    final Thread current = Thread.currentThread();
    //  獲取state狀態(tài)
    int c = getState();
    //  state是不是沒有線程持有鎖資源
    if (c == 0) {
        //  當前鎖資源沒有被其他線程持有
        //  hasQueuedPredecessors方法: 鎖資源沒有被持有,進入隊列排隊
        //  排隊規(guī)則:
        //  1. 檢查隊列沒有線程排隊,搶鎖。
        //  2. 檢查隊列有線程排隊,查看當前線程是否排在第一位,如果是搶鎖,否則入隊列(注:該方法只是判斷,沒有真正入隊列)
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            //  再次CAS操作嘗試, 成功就設置互斥鎖的為當前線程擁有
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //  鎖資源是否被當前線程所持有 (可重入鎖)
    else if (current == getExclusiveOwnerThread()) {
        //  持有鎖資源為當前, 則對state + 1
        int nextc = c + acquires;
        //  健壯性判斷
        if (nextc < 0)
            //  超過最大鎖重入次數(shù)會拋異常(幾率很小,理論上存在)
            throw new Error("Maximum lock count exceeded");
        //  設置state狀態(tài),代表鎖重入成功
        setState(nextc);
        return true;
    }
    return false;
}

四.  AQS的addWaiter方法

 addWaiter方法,就是將當前線程封裝為Node對象,并且插入到AQS的雙向鏈表。

//  線程入隊列排隊
private Node addWaiter(Node mode) {
    //  將當前對象封裝為Node對象 
    //  Node.EXCLUSIVE 表示互斥  Node.SHARED 表示共享
    Node node = new Node(Thread.currentThread(), mode);
    // 獲取tail節(jié)點
    Node pred = tail;
    //  判斷雙向鏈表隊列有沒有初始化
    if (pred != null) {
        //  將當前線程封裝的Node節(jié)點prev屬性指向tail尾節(jié)點
        node.prev = pred;
        //  通過CAS操作設置當前線程封裝的Node節(jié)點為尾節(jié)點
        if (compareAndSetTail(pred, node)) {
            //  成功則將上一個尾節(jié)點的next屬性指向當前線程封裝的Node節(jié)點
            pred.next = node;
            return node;
        }
    }
    //  沒有初始化head 和 tail 都等于null
    //  enq方法: 插入雙向鏈表和初始化雙向鏈表
    enq(node);
    //  完成節(jié)點插入
    return node;
}
//  插入雙向鏈表和初始化雙向鏈表
private Node enq(final Node node) {
    //  死循環(huán) 
    for (;;) {
        //  獲取當前tail節(jié)點
        Node t = tail;
        //  判斷尾節(jié)點是否初始
        if (t == null) { // Must initialize
            //  通過CAS操作初始化初始化一個虛擬的Node節(jié)點,賦給head節(jié)點
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //  完成當前線程Node節(jié)點加入AQS雙向鏈表的過程
            //  當前線程封裝的Node的上一個prev屬性指向tail節(jié)點
            //  流程: 1. prev(私有)  --->  2. tail(共有)  ---> 3. next (共有)
            node.prev = t;
            //  通過CAS操作修改tail尾節(jié)點指向當前線程封裝的Node
            if (compareAndSetTail(t, node)) {
                //  將當前線程封裝的Node節(jié)點賦給上一個Node的下一個next屬性
                t.next = node;
                return t;
            }
        }
    }
}

五.  AQS的acquireQueued方法

acquireQueued方法主要就是線程掛起以及重新嘗試獲取鎖資源的地方

重新獲取鎖資源主要有兩種情況:

  • 上來就排在head.next,就回去嘗試拿鎖
  • 喚醒之后嘗試拿鎖
//  當前線程Node添加到AQS隊列后續(xù)操作
final boolean acquireQueued(final Node node, int arg) {
    //  標記,記錄拿鎖狀態(tài)   失敗
    boolean failed = true;
    try {
        // 中斷狀態(tài)
        boolean interrupted = false;
        //  死循環(huán)
        for (;;) {
            //  獲取當前節(jié)點的上一個節(jié)點    prev
            final Node p = node.predecessor();
            //  判斷當前節(jié)點是否是head,是則代表當前節(jié)點排在第一位
            //  如果是第一位,執(zhí)行tryAcquire方法嘗試拿鎖
            if (p == head && tryAcquire(arg)) {
                //  都成功,代表拿到鎖資源
                //  將當前線程Node設置為head節(jié)點,同時將Node的thread 和 prev屬性設置為null
                setHead(node);
                //  將上一個head的next屬性設置為null,等待GC回收
                p.next = null; // help GC
                //  拿鎖狀態(tài)  成功
                failed = false;
                //  返回中斷狀態(tài)
                return interrupted;
            }
            //  沒有獲取到鎖 --- 嘗試掛起線程
            //  shouldParkAfterFailedAcquire方法: 掛起線程前的準備
            //  parkAndCheckInterrupt方法: 掛起當前線程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //  設置中斷線程狀態(tài)
                interrupted = true;
        }
    } finally {
        //  取消節(jié)點
        if (failed)
            cancelAcquire(node);
    }
}
//  檢查并更新無法獲取鎖節(jié)點的狀態(tài)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //  獲取上一個節(jié)點的ws狀態(tài)
    /**
    * SIGNAL(-1)   表示當前節(jié)點釋放鎖的時候,需要喚醒下一個節(jié)點?;蛘哒f后繼節(jié)點在等待當前節(jié)點喚醒,后繼節(jié)點入隊時候,會將前驅節(jié)點更新給signal。
    * CANCELLED(1)  表示當前節(jié)點已取消調度。當timeout或者中斷情況下,會觸發(fā)變更為此狀態(tài),進入該狀態(tài)后的節(jié)點不再變化。
    * CONDITION(-2)  當其他線程調用了condition的signal方法后,condition狀態(tài)的節(jié)點會從等待隊列轉移到同步隊列中,等待獲取同步鎖。
    * PROPAGATE(-3)   表示共享模式下,前驅節(jié)點不僅會喚醒其后繼節(jié)點,同時也可能喚醒后繼的后繼節(jié)點。
    * 默認(0) 新節(jié)點入隊時候的默認狀態(tài)。
    */
    int ws = pred.waitStatus;
    //  判斷上個節(jié)點ws狀態(tài)是否是 -1, 是則掛起    
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        /** 
        * 判斷上個節(jié)點是否是取消或者其他狀態(tài)。
        * 向前找到不是取消狀態(tài)的節(jié)點,修改ws狀態(tài)。
        * 注意:那些放棄的結點,由于被自己“加塞”到它們前邊,它們相當于形成一個無引用鏈,
        * 稍后就會被GC回收,這個操作實際是把隊列中的cancelled節(jié)點剔除掉。
        */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //  如果前驅節(jié)點正常,那就把上一個節(jié)點的狀態(tài)通過CAS的方式設置成-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
//  掛起當前線程
private final boolean parkAndCheckInterrupt() {
    //  掛起當前線程
    LockSupport.park(this);
    //  返回中斷標志
    return Thread.interrupted();
}

六.  AQS的Lock鎖的release方法

//  互斥鎖模式   解鎖
public final boolean release(int arg) {
    //  嘗試是否可以解鎖
    if (tryRelease(arg)) {
        Node h = head;
        //  判斷雙鏈表是否存在線程排隊
        if (h != null && h.waitStatus != 0)
            //  喚醒后續(xù)線程
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//  嘗試是否可以解鎖
protected final boolean tryRelease(int releases) {
    //  鎖狀態(tài) =  狀態(tài) - 1
    int c = getState() - releases;
    //  判斷鎖是是否是當前線程持有
    if (Thread.currentThread() != getExclusiveOwnerThread())
        //  當前線程沒有持有拋出異常
        throw new IllegalMonitorStateException();
    boolean free = false;
    //  當前鎖狀態(tài)變?yōu)?,則清空鎖歸屬線程
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    //  設置鎖狀態(tài)為0
    setState(c);
    return free;
}
//  喚醒線程
private void unparkSuccessor(Node node) {
    //  獲取頭節(jié)點的狀態(tài)
    int ws = node.waitStatus;
    if (ws < 0)
        //  通過CAS將頭節(jié)點的狀態(tài)設置為初始狀態(tài)
        compareAndSetWaitStatus(node, ws, 0);
    //  后繼節(jié)點
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        //  從尾節(jié)點開始往前遍歷,尋找離頭節(jié)點最近的等待狀態(tài)正常的節(jié)點
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //  真正的喚醒操作
        LockSupport.unpark(s.thread);
}

到此這篇關于淺析Java ReentrantLock鎖的原理與使用的文章就介紹到這了,更多相關Java ReentrantLock鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論