最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java中ReentrantLock的用法和原理

 更新時間:2023年06月07日 10:02:24   作者:IT利刃出鞘  
本文主要介紹了Java中ReentrantLock的用法和原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

簡介

說明

本文介紹Java的JUC中的ReentrantLock(可重入獨占式鎖)。包括:用法、原理。

概述

ReentrantLock主要利用AQS隊列來實現(xiàn)。它支持公平鎖和非公平鎖。

AQS隊列使用了CAS,所以ReentrantLock有CAS的優(yōu)缺點。優(yōu)點:性能高。缺點:CPU占用高。

ReentrantLock的流程

  • state初始化為0,表示未鎖定狀態(tài)
  • A線程lock()時,會調用tryAcquire()獲取鎖并將state+1
  • 其他線程tryAcquire獲取鎖會失敗,直到A線程unlock() 到state=0,其他線程才有機會獲取該鎖。
  • A釋放鎖之前,自己可以重復獲取此鎖(state累加),這就是可重入的概念。

注意:獲取多少次鎖就要釋放多少次鎖,保證state能回到0。 

示例

private Lock lock = new ReentrantLock();
public void test(){
    lock.lock();
    try{
        doSomeThing();
    }catch (Exception e){
        // ignored
    }finally {
        lock.unlock();
    }
}

公平與非公平

ReentrantLock的默認實現(xiàn)是非公平鎖,但是也可以設置為公平鎖。

非公平鎖

如果同時還有另一個線程進來嘗試獲取,那么有可能會讓這個線程搶先獲??;

公平鎖

如果同時還有另一個線程進來嘗試獲取,當它發(fā)現(xiàn)自己不是在隊首的話,就會排到隊尾,由隊首的線程獲取到鎖。

ReentrantLock提供了兩個構造器,分別是

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

NonfairSync的lock()方法

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

首先用一個CAS操作,判斷state是否是0(表示當前鎖未被占用),如果是0則把它置為1,并且設置當前線程為該鎖的獨占線程,表示獲取鎖成功。當多個線程同時嘗試占用同一個鎖時,CAS操作只能保證一個線程操作成功,剩下的只能去排隊啦。

“非公平”即體現(xiàn)在這里,如果占用鎖的線程剛釋放鎖,state置為0,而排隊等待鎖的線程還未喚醒時,新來的線程就直接搶占了該鎖,那么就“插隊”了。

FairSync的lock()方法

final void lock() {
    acquire(1);
}

直接調用acquire(1)方法。

非公平鎖lock()原理

場景

簡述:A線程獲得鎖,B和C線程失敗,B和C執(zhí)行acquire(1);

本處以非公平鎖(NonfairSync)示例進行講解,假設有如下場景:有三個線程去競爭鎖,假設線程A的CAS操作成功了,拿到了鎖開開心心的返回了,那么線程B和C則設置state失敗,走到了else里面。

NonfairSync的lock()方法

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

 acquire()方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

(1)嘗試獲得鎖

簡述:嘗試去獲取鎖。如果嘗試獲取鎖成功,方法直接返回。

非公平鎖tryAcquire的流程是:

檢查state字段;

若為0:表示鎖未被占用,那么嘗試占用;
若不為0:檢查當前鎖是否被自己占用,若是,則state+1(重入鎖的次數(shù))。

若以上兩點都失敗,則獲取鎖失敗,返回false。

tryAcquire(arg)
final boolean nonfairTryAcquire(int acquires) {
    //獲取當前線程
    final Thread current = Thread.currentThread();
    //獲取state變量值
    int c = getState();
    if (c == 0) { //沒有線程占用鎖
        if (compareAndSetState(0, acquires)) {
            //占用鎖成功,設置獨占線程為當前線程
            setExclusiveOwnerThread(current);
            return true;
        }
    } else if (current == getExclusiveOwnerThread()) { //當前線程已經占用該鎖
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 更新state值為新的重入次數(shù)
        setState(nextc);
        return true;
    }
    //獲取鎖失敗
    return false;
}

(2)入隊

由于上文中提到線程A已經占用了鎖,所以B和C執(zhí)行tryAcquire失敗,并且入等待隊列(acquireQueued(addWaiter(Node.EXCLUSIVE), arg)))。如果線程A拿著鎖死死不放,那么B和C就會被掛起。

先看下入隊的過程。先看addWaiter(Node.EXCLUSIVE)  

/**
 * 將新節(jié)點和當前線程關聯(lián)并且入隊列
 * @param mode 獨占/共享
 * @return 新節(jié)點
 */
private Node addWaiter(Node mode) {
    //初始化節(jié)點,設置關聯(lián)線程和模式(獨占 or 共享)
    Node node = new Node(Thread.currentThread(), mode);
    // 獲取尾節(jié)點引用
    Node pred = tail;
    // 尾節(jié)點不為空,說明隊列已經初始化過
    if (pred != null) {
        node.prev = pred;
        // 設置新節(jié)點為尾節(jié)點
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 尾節(jié)點為空,說明隊列還未初始化,需要初始化head節(jié)點并入隊新節(jié)點
    enq(node);
    return node;
}

B、C線程同時嘗試入隊列,由于隊列尚未初始化,tail==null,故至少會有一個線程會走到enq(node)。我們假設同時走到了enq(node)里。

/**
 * 初始化隊列并且入隊新節(jié)點
 */
private Node enq(final Node node) {
    //開始自旋
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // 如果tail為空,則新建一個head節(jié)點,并且tail指向head
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            // tail不為空,將新節(jié)點入隊
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

這里體現(xiàn)了經典的自旋+CAS組合來實現(xiàn)非阻塞的原子操作。由于compareAndSetHead的實現(xiàn)使用了unsafe類提供的CAS操作,所以只有一個線程會創(chuàng)建head節(jié)點成功。假設線程B成功,之后B、C開始第二輪循環(huán),此時tail已經不為空,兩個線程都走到else里面。假設B線程compareAndSetTail成功,那么B就可以返回了,C由于入隊失敗還需要第三輪循環(huán)。最終所有線程都可以成功入隊。

當B、C入等待隊列后,此時AQS隊列如下:

(3)掛起

B和C相繼執(zhí)行acquireQueued(final Node node, int arg)。這個方法讓已經入隊的線程嘗試獲取鎖,若失敗則會被掛起。

/**
 * 已經入隊的線程嘗試獲取鎖
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true; //標記是否成功獲取鎖
    try {
        boolean interrupted = false; //標記線程是否被中斷過
        for (;;) {
            final Node p = node.predecessor(); //獲取前驅節(jié)點
            //如果前驅是head,即該結點已成老二,那么便有資格去嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node); // 獲取成功,將當前節(jié)點設置為head節(jié)點
                p.next = null; // 原h(huán)ead節(jié)點出隊,在某個時間點被GC回收
                failed = false; //獲取成功
                return interrupted; //返回是否被中斷過
            }
            // 判斷獲取失敗后是否可以掛起,若可以則掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                // 線程若被中斷,設置interrupted為true
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

code里的注釋已經很清晰的說明了acquireQueued的執(zhí)行流程。假設B和C在競爭鎖的過程中A一直持有鎖,那么它們的tryAcquire操作都會失敗,因此會走到第2個if語句中。 

再看下shouldParkAfterFailedAcquire和parkAndCheckInterrupt流程吧

/**
 * 判斷當前線程獲取鎖失敗之后是否需要掛起.
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //前驅節(jié)點的狀態(tài)
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驅節(jié)點狀態(tài)為signal,返回true
        return true;
    // 前驅節(jié)點狀態(tài)為CANCELLED
    if (ws > 0) {
        // 從隊尾向前尋找第一個狀態(tài)不為CANCELLED的節(jié)點
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 將前驅節(jié)點的狀態(tài)設置為SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
/**
 * 掛起當前線程,返回線程中斷狀態(tài)并重置
 */
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

線程入隊后能夠掛起的前提是,它的前驅節(jié)點的狀態(tài)為SIGNAL,它的含義是:“Hi,前面的兄弟,如果你獲取鎖并且出隊后,記得把我喚醒!”。所以shouldParkAfterFailedAcquire會先判斷當前節(jié)點的前驅是否狀態(tài)符合要求,若符合則返回true,然后調用parkAndCheckInterrupt,將自己掛起;如果不符合,再看前驅節(jié)點是否>0(CANCELLED),若是那么向前遍歷直到找到第一個符合要求(狀態(tài)不大于0)的前驅,若不是則將前驅節(jié)點的狀態(tài)設置為SIGNAL。

整個流程中,如果前驅結點的狀態(tài)不是SIGNAL,那么自己就不能安心掛起,需要去找個安心的掛起點,同時可以再嘗試下看有沒有機會去嘗試競爭鎖。

最終隊列可能會如下圖所示

總結

用一張流程圖總結一下非公平鎖的獲取鎖的過程。 

非公平鎖unlock()原理

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

如果理解了加鎖的過程,那么解鎖看起來就容易多了。流程大致為先嘗試釋放鎖,若釋放成功,那么查看頭結點的狀態(tài)是否為SIGNAL,如果是則喚醒頭結點的下個節(jié)點關聯(lián)的線程,如果釋放失敗那么返回false表示解鎖失敗。這里我們也發(fā)現(xiàn)了,每次都只喚起頭結點的下一個節(jié)點關聯(lián)的線程。

最后我們再看下tryRelease的執(zhí)行過程

/**
 * 釋放當前線程占用的鎖
 * @param releases
 * @return 是否釋放成功
 */
protected final boolean tryRelease(int releases) {
    // 計算釋放后state值
    int c = getState() - releases;
    // 如果不是當前線程占用鎖,那么拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        // 鎖被重入次數(shù)為0,表示釋放成功
        free = true;
        // 清空獨占線程
        setExclusiveOwnerThread(null);
    }
    // 更新state值
    setState(c);
    return free;
}

這里入參為1。tryRelease的過程為:當前釋放鎖的線程若不持有鎖,則拋出異常。若持有鎖,計算釋放后的state值是否為0,若為0表示鎖已經被成功釋放,并且則清空獨占線程,最后更新state值,返回free。     

公平鎖原理

公平鎖和非公平鎖不同之處在于,公平鎖在獲取鎖的時候,不會先去檢查state狀態(tài),而是直接執(zhí)行aqcuire(1);

超時機制

在ReetrantLock的tryLock(long timeout, TimeUnit unit) 提供了超時獲取鎖的功能。它的語義是在指定的時間內如果獲取到鎖就返回true,獲取不到則返回false。這種機制避免了線程無限期的等待鎖釋放。那么超時的功能是怎么實現(xiàn)的呢?我們還是用非公平鎖為例來一探究竟。

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

還是調用了內部類里面的方法。我們繼續(xù)向前探究 

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

這里的語義是:如果線程被中斷了,那么直接拋出InterruptedException。如果未中斷,先嘗試獲取鎖,獲取成功就直接返回,獲取失敗則進入doAcquireNanos。tryAcquire我們已經看過,這里重點看一下doAcquireNanos做了什么。 

/**
 * 在有限的時間內去競爭鎖
 * @return 是否獲取成功
 */
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    // 起始時間
    long lastTime = System.nanoTime();
    // 線程入隊
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        // 又是自旋!
        for (;;) {
            // 獲取前驅節(jié)點
            final Node p = node.predecessor();
            // 如果前驅是頭節(jié)點并且占用鎖成功,則將當前節(jié)點變成頭結點
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 如果已經超時,返回false
            if (nanosTimeout <= 0)
                return false;
            // 超時時間未到,且需要掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                // 阻塞當前線程直到超時時間到期
                LockSupport.parkNanos(this, nanosTimeout);
            long now = System.nanoTime();
            // 更新nanosTimeout
            nanosTimeout -= now - lastTime;
            lastTime = now;
            if (Thread.interrupted())
                //相應中斷
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

doAcquireNanos的流程簡述為:線程先入等待隊列,然后開始自旋,嘗試獲取鎖,獲取成功就返回,失敗則在隊列里找一個安全點把自己掛起直到超時時間過期。這里為什么還需要循環(huán)呢?因為當前線程節(jié)點的前驅狀態(tài)可能不是SIGNAL,那么在當前這一輪循環(huán)中線程不會被掛起,然后更新超時時間,開始新一輪的嘗試 

輪詢與中斷

ReentrantLock被保留了下來的原因是:ReentrantLock比synchronied多了兩個功能:可輪詢、可中斷。

1、可輪詢

原書上面的例子看著比較復雜,但意思很簡單。一個轉賬的操作,要么在規(guī)定的時間內完成,要么在規(guī)定的時間內告訴調用者,操作沒有完成。這個例子就是要了ReentrantLock的可輪詢特性,就是在規(guī)定的時間內,反復去試圖獲得一個鎖,如果獲得成功,就能完成轉賬操作,如果在規(guī)定的時間內,沒有獲得這個鎖,那么就是轉賬失敗。如果使用synchronized的話,肯定是無法做到的。

public boolean transferMoney(Account fromAcct,  
                             Account toAcct,  
                             DollarAmount amount,  
                             long timeout,  
                             TimeUnit unit)  
        throws InsufficientFundsException, InterruptedException {  
    long fixedDelay = getFixedDelayComponentNanos(timeout, unit);  
    long randMod = getRandomDelayModulusNanos(timeout, unit);  
    long stopTime = System.nanoTime() + unit.toNanos(timeout);  
    while (true) {  
        if (fromAcct.lock.tryLock()) {  
            try {  
                if (toAcct.lock.tryLock()) {  
                    try {  
                        if (fromAcct.getBalance().compareTo(amount) < 0)  
                            throw new InsufficientFundsException();  
                        else {  
                            fromAcct.debit(amount);  
                            toAcct.credit(amount);  
                            return true;  
                        }  
                    } finally {  
                        toAcct.lock.unlock();  
                    }  
                 }  
             } finally {  
                 fromAcct.lock.unlock();  
             }  
         }  
         if (System.nanoTime() < stopTime)  
             return false;  
         NANOSECONDS.sleep(fixedDelay + rnd.nextLong() % randMod);  
     }  
}  

 2、可中斷

在synchronied的代碼中,進入臨界區(qū)的代碼是無法中斷的,這個很不靈活,如果我們使用一個線程池來分發(fā)任務,如果一個代碼長期占有鎖肯定會影響到線程池的其他任務,因此,加入中斷機制提高了對任務更強的控制性。

public boolean sendOnSharedLine(String message)  
        throws InterruptedException {  
    lock.lockInterruptibly();  
    try {  
        return cancellableSendOnSharedLine(message);  
    } finally {  
        lock.unlock();  
    }  
}  
private boolean cancellableSendOnSharedLine(String message)  
    throws InterruptedException { ... }

公平性:ReentrantLock默認采用非公平鎖,synchronized鎖也是采用的非公平鎖。
如果你沒有要求鎖有可輪詢和可中斷的需求,還是使用synchronized內置鎖吧。

其他網址

ReentrantLock原理_Java_路漫漫,水迢迢-CSDN博客
慎用ReentrantLock

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

相關文章

  • springSecurity使用實戰(zhàn)指南

    springSecurity使用實戰(zhàn)指南

    本文系統(tǒng)講解了Spring Security的核心功能,包括實戰(zhàn)舉例,本文結合實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2025-08-08
  • Mybatis如何自動生成數(shù)據(jù)庫表的實體類

    Mybatis如何自動生成數(shù)據(jù)庫表的實體類

    這篇文章主要介紹了Mybatis自動生成數(shù)據(jù)庫表的實體類的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java實現(xiàn)Huffman編碼的示例代碼

    Java實現(xiàn)Huffman編碼的示例代碼

    Huffman編碼是一種編碼方式,本文主要介紹了Java實現(xiàn)Huffman編碼的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • java 內部類的詳解及實例

    java 內部類的詳解及實例

    這篇文章主要介紹了 java 內部類的詳解及實例的相關資料,這里提供了兩種內部類的實現(xiàn)方法,并給出實例,需要的朋友可以參考下
    2017-08-08
  • Hadoop多Job并行處理的實例詳解

    Hadoop多Job并行處理的實例詳解

    這篇文章主要介紹了Hadoop多Job并行處理的實例詳解的相關資料,希望通過本文能幫助到大家,需要的朋友可以參考下
    2017-10-10
  • EditPlus運行java時從鍵盤輸入數(shù)據(jù)的操作方法

    EditPlus運行java時從鍵盤輸入數(shù)據(jù)的操作方法

    這篇文章主要介紹了EditPlus運行java時從鍵盤輸入數(shù)據(jù)的操作方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • Java漢字轉成漢語拼音工具類

    Java漢字轉成漢語拼音工具類

    這篇文章主要為大家詳細介紹了Java漢字轉成漢語拼音工具類,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Spring自動配置之condition條件判斷下篇

    Spring自動配置之condition條件判斷下篇

    這篇文章主要為大家介紹了SpringBoot?condition條件判斷功能的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-08-08
  • Java中關于isEmpty方法、null以及““的區(qū)別

    Java中關于isEmpty方法、null以及““的區(qū)別

    這篇文章主要介紹了Java中關于isEmpty方法、null以及““的區(qū)別,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • Spring Boot啟動流程分析

    Spring Boot啟動流程分析

    本文給大家介紹spring boot是怎樣啟動和啟動做了哪些事情。具體內容詳情大家通過本文詳細學習吧
    2017-09-09

最新評論

化德县| 齐齐哈尔市| 南开区| 确山县| 满洲里市| 汉阴县| 三门县| 原平市| 都兰县| 武冈市| 讷河市| 黑河市| 巨野县| 巨鹿县| 银川市| 昌邑市| 思茅市| 乐东| 凤翔县| 广平县| 拉萨市| 成都市| 托克托县| 宁化县| 棋牌| 麻栗坡县| 绿春县| 探索| 宝鸡市| 福建省| 报价| 凭祥市| 平乡县| 安新县| 大理市| 望城县| 岑巩县| 镇巴县| 河南省| 武功县| 垦利县|