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

詳解Java?ReentrantLock可重入,可打斷,鎖超時的實現(xiàn)原理

 更新時間:2022年10月13日 09:44:59   作者:JAVA旭陽  
前面講解了ReentrantLock加鎖和解鎖的原理實現(xiàn),但是沒有闡述它的可重入、可打斷以及超時獲取鎖失敗的原理,本文就重點講解這三種情況,需要的可以了解一下

概述

前面講解了ReentrantLock加鎖和解鎖的原理實現(xiàn),但是沒有闡述它的可重入、可打斷以及超時獲取鎖失敗的原理,本文就重點講解這三種情況。建議大家先看下這篇文章了解下ReentrantLock加鎖的基本原理,圖解Java ReentrantLock公平鎖和非公平鎖的實現(xiàn)

可重入

可重入是指一個線程如果獲取了鎖,那么它就是鎖的主人,那么它可以再次獲取這把鎖,這種就是理解為重入,簡而言之,可以重復(fù)獲取同一把鎖,不會造成阻塞,舉個例子如下:

@Test
    public void testRepeatLock() {
        ReentrantLock reentrantLock = new ReentrantLock();
        // 第一次獲取鎖
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " first get lock");
            // 再次獲取鎖
            tryAgainLock(reentrantLock);
        }finally {
            reentrantLock.unlock();
        }
    }

    public void tryAgainLock(ReentrantLock reentrantLock) {
        // 第2次獲取鎖
        reentrantLock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " second get lock");
        }finally {
            reentrantLock.unlock();
        }
    }

  • 同一個線程使用ReentrantLock多次獲取鎖,不會阻塞
  • 申請幾把鎖,最后需要解除幾把鎖

那你知道是怎么實現(xiàn)的嗎?

概述的文章中已經(jīng)講解了ReentrantLock整個的加鎖和解鎖的過程,可重入實現(xiàn)就在其中,這里著重關(guān)注下申請鎖的方法tryAcquire,最終會調(diào)用nonfairTryAcquire方法。

  • 如果已經(jīng)有線程獲得了鎖, 并且占用鎖的線程是當(dāng)前線程, 表示【發(fā)生了鎖重入】,上圖的1步驟
  • 計算出沖入的次數(shù)nextc等于當(dāng)前次數(shù)+新增次數(shù),acquires等于1
  • 更新 state 的值,這里不使用 cas 是因為當(dāng)前線程正在持有鎖,所以這里的操作相當(dāng)于在一個管程內(nèi), 然后返回ture,表明再次申請鎖成功。

可打斷

ReentrantLock相比于synchronized加鎖一大優(yōu)勢是可打斷,那么什么是可打斷呢?ReentrantLock通過lockInterruptibly()加鎖,如果一直獲取不到鎖,可以通過調(diào)用線程的interrupt()提前終止線程。舉個例子:

@Test
    public void testInterrupt() throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();

        // 主線程普通加鎖
        System.out.println("主線程優(yōu)先獲取鎖");
        lock.lock();
        try {
            // 創(chuàng)建子線程
            Thread t1 = new Thread(() -> {
                try {
                    System.out.println("t1嘗試獲取打斷鎖");
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    System.out.println("t1沒有獲取到鎖,被打斷,直接返回");
                    return;
                }
                try {
                    System.out.println("t1成功獲取鎖");
                } finally {
                    System.out.println("t1釋放鎖");
                    lock.unlock();
                }
            }, "t1");
            t1.start();
            Thread.sleep(2000);
            System.out.println("主線程進行打斷鎖");
            t1.interrupt();
        } finally {
            // 主線程解鎖
            System.out.println("主線程優(yōu)先釋放鎖");
            lock.unlock();
        }
    }

  • 通過lockInterruptibly()方法獲取鎖期間,可以通過線程的interrupt()方法進行中斷,跳出阻塞。
  • 通過lock()方法獲取鎖,不會響應(yīng)interrupt()方法的中斷。

接下來我們看看它的實現(xiàn)原理。

public void lockInterruptibly() throws InterruptedException {    
    sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg) {
    // 被其他線程打斷了直接返回 false
    if (Thread.interrupted())
		throw new InterruptedException();
    if (!tryAcquire(arg))
        // 沒獲取到鎖,進入這里
        doAcquireInterruptibly(arg);
}
  • 先判斷一次線程是否中斷了,是的話,直接拋出中斷異常。
  • 如果沒有獲取鎖,調(diào)用doAcquireInterruptibly()方法。
private void doAcquireInterruptibly(int arg) throws InterruptedException {
    // 封裝當(dāng)前線程,加入到隊列中
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        // 自旋
        for (;;) {
            // shouldParkAfterFailedAcquire判斷是否需要阻塞等待
            // parkAndCheckInterrupt方法是阻塞線程,返回true,表示線程被中斷了
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                // 【在 park 過程中如果被 interrupt 會拋出異常】, 而不會再次進入循環(huán)獲取鎖后才完成打斷效果
                throw new InterruptedException();
        }    
    } finally {
        // 拋出異常前會進入這里
        if (failed)
            // 取消當(dāng)前線程的節(jié)點
            cancelAcquire(node);
    }
}
  • addWaiter將當(dāng)前線程封裝成節(jié)點,加入到隊列中。
  • shouldParkAfterFailedAcquire()方法判斷如果前一個節(jié)點的等待狀態(tài)時-1,則返回true,表示當(dāng)前線程需要阻塞。
  • parkAndCheckInterrupt()方法是阻塞線程,返回true,表示線程被中斷了,拋出InterruptedException異常。
  • 最后調(diào)用cancelAcquire()方法,將當(dāng)前節(jié)點狀態(tài)設(shè)置為cancel取消狀態(tài)。
// 取消節(jié)點出隊的邏輯
private void cancelAcquire(Node node) {
    // 判空
    if (node == null)
        return;
	// 把當(dāng)前節(jié)點封裝的 Thread 置為空
    node.thread = null;
	// 獲取當(dāng)前取消的 node 的前驅(qū)節(jié)點
    Node pred = node.prev;
    // 前驅(qū)節(jié)點也被取消了,循環(huán)找到前面最近的沒被取消的節(jié)點
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
    
	// 獲取前驅(qū)節(jié)點的后繼節(jié)點,可能是當(dāng)前 node,也可能是 waitStatus > 0 的節(jié)點
    Node predNext = pred.next;
    
	// 把當(dāng)前節(jié)點的狀態(tài)設(shè)置為 【取消狀態(tài) 1】
    node.waitStatus = Node.CANCELLED;
    
	// 條件成立說明當(dāng)前節(jié)點是尾節(jié)點,把當(dāng)前節(jié)點的前驅(qū)節(jié)點設(shè)置為尾節(jié)點
    if (node == tail && compareAndSetTail(node, pred)) {
        // 把前驅(qū)節(jié)點的后繼節(jié)點置空,這里直接把所有的取消節(jié)點出隊
        compareAndSetNext(pred, predNext, null);
    } else {
        // 說明當(dāng)前節(jié)點不是 tail 節(jié)點
        int ws;
        // 條件一成立說明當(dāng)前節(jié)點不是 head.next 節(jié)點
        if (pred != head &&
            // 判斷前驅(qū)節(jié)點的狀態(tài)是不是 -1,不成立說明前驅(qū)狀態(tài)可能是 0 或者剛被其他線程取消排隊了
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             // 如果狀態(tài)不是 -1,設(shè)置前驅(qū)節(jié)點的狀態(tài)為 -1
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            // 前驅(qū)節(jié)點的線程不為null
            pred.thread != null) {
            
            Node next = node.next;
            // 當(dāng)前節(jié)點的后繼節(jié)點是正常節(jié)點
            if (next != null && next.waitStatus <= 0)
                // 把 前驅(qū)節(jié)點的后繼節(jié)點 設(shè)置為 當(dāng)前節(jié)點的后繼節(jié)點,【從隊列中刪除了當(dāng)前節(jié)點】
                compareAndSetNext(pred, predNext, next);
        } else {
            // 當(dāng)前節(jié)點是 head.next 節(jié)點,喚醒當(dāng)前節(jié)點的后繼節(jié)點
            unparkSuccessor(node);
        }
        node.next = node; // help GC
    }
}

鎖超時

ReentrantLock還具備鎖超時的能力,調(diào)用tryLock(long timeout, TimeUnit unit)方法,在給定時間內(nèi)獲取鎖,獲取不到就退出,這也是synchronized沒有的功能。

@Test
    public void testLockTimeout() throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            try {
                // 調(diào)用tryLock獲取鎖
                if (!lock.tryLock(2, TimeUnit.SECONDS)) {
                    System.out.println("t1獲取不到鎖");
                    return;
                }
            } catch (InterruptedException e) {
                System.out.println("t1被打斷,獲取不到鎖");
                return;
            }
            try {
                System.out.println("t1獲取到鎖");
            } finally {
                lock.unlock();
            }
        }, "t1");
        // 主線程加鎖
        lock.lock();
        System.out.println("主線程獲取到鎖");

        t1.start();
        Thread.sleep(3000);
        try {
            System.out.println("主線程釋放了鎖");
        } finally {
            lock.unlock();
        }
    }

那這個原理實現(xiàn)是什么樣的呢?

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    // 調(diào)用tryAcquireNanos方法
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public final boolean tryAcquireNanos(int arg, long nanosTimeout) {
    if (Thread.interrupted())        
        throw new InterruptedException();    
    // tryAcquire 嘗試一次,獲取不到的話調(diào)用doAcquireNanos方法
    return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

protected final boolean tryAcquire(int acquires) {    
    return nonfairTryAcquire(acquires);
}
private boolean doAcquireNanos(int arg, long nanosTimeout) {    
    if (nanosTimeout <= 0L)
        return false;
    // 獲取最后期限的時間戳
    final long deadline = System.nanoTime() + nanosTimeout;
    // 將當(dāng)前線程添加到隊列中
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        // 自旋
        for (;;) {
            // 獲取前驅(qū)節(jié)點
            final Node p = node.predecessor();
            // 前驅(qū)節(jié)點是head,嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            // 計算還需等待的時間
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)	//時間已到     
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 如果 nanosTimeout 大于該值,才有阻塞的意義,否則直接自旋會好點
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            // 【被打斷會報異?!?
            if (Thread.interrupted())
                throw new InterruptedException();
        }    
    }
}
  • 如果nanosTimeout小于0,表示到了指定時間沒有獲取鎖成功,返回false
  • 如果 nanosTimeout 大于spinForTimeoutThreshold,值為1000L,進行阻塞。因為時間太短阻塞沒有意義,否則直接自旋會好點。

到此這篇關(guān)于詳解Java ReentrantLock可重入,可打斷,鎖超時的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Java ReentrantLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于Java實現(xiàn)的Dijkstra算法示例

    基于Java實現(xiàn)的Dijkstra算法示例

    這篇文章主要介紹了基于Java實現(xiàn)的Dijkstra算法示例,一個比較典型的算法示例,需要的朋友可以參考下
    2014-07-07
  • 使用Springboot 打jar包實現(xiàn)分離依賴lib和配置

    使用Springboot 打jar包實現(xiàn)分離依賴lib和配置

    這篇文章主要介紹了使用Springboot 打jar包實現(xiàn)分離依賴lib和配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • JavaFX桌面應(yīng)用未響應(yīng)問題解決方案

    JavaFX桌面應(yīng)用未響應(yīng)問題解決方案

    這篇文章主要介紹了JavaFX桌面應(yīng)用未響應(yīng)問題解決方案,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-07-07
  • 淺談Java中的克隆close()和賦值引用的區(qū)別

    淺談Java中的克隆close()和賦值引用的區(qū)別

    下面小編就為大家?guī)硪黄獪\談Java中的克隆close()和賦值引用的區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • 關(guān)于ThreadLocal和InheritableThreadLocal解析

    關(guān)于ThreadLocal和InheritableThreadLocal解析

    這篇文章主要介紹了關(guān)于ThreadLocal和InheritableThreadLocal解析,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • springboot中的多個application文件講解

    springboot中的多個application文件講解

    這篇文章主要介紹了springboot中的多個application文件,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Maven依賴管理之parent與dependencyManagement深入分析

    Maven依賴管理之parent與dependencyManagement深入分析

    首先我們來說說parent標簽,其實這個不難解釋,就是父的意思,pom也有繼承的。比方說我現(xiàn)在有A,B,C,A是B,C的父級?,F(xiàn)在就是有一個情況B,C其實有很多jar都是共同的,其實是可以放在父項目里面,這樣,讓B,C都繼承A就方便管理了
    2022-10-10
  • java: 程序包com.fasterxml.jackson.annotation不存在的解決辦法

    java: 程序包com.fasterxml.jackson.annotation不存在的解決辦法

    當(dāng)我們在導(dǎo)入程序之后,系統(tǒng)給出錯誤提示:java: 程序包com.fasterxml.jackson.annotation不存在,本文主要介紹了Java程序包不存在的三種解決方法,需要的朋友可以參考下
    2024-02-02
  • Spark JDBC操作MySQL方式詳細講解

    Spark JDBC操作MySQL方式詳細講解

    這篇文章主要介紹了Spark JDBC操作MySQL方式,Spark SQL可以通過JDBC從傳統(tǒng)的關(guān)系型數(shù)據(jù)庫中讀寫數(shù)據(jù),讀取數(shù)據(jù)后直接生成的是DataFrame,然后再加上借助于Spark SQL豐富的API來進行各種操作
    2023-02-02
  • 通過Java實現(xiàn)bash命令過程解析

    通過Java實現(xiàn)bash命令過程解析

    這篇文章主要介紹了通過Java實現(xiàn)bash命令過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01

最新評論

乌拉特中旗| 宝山区| 鄂州市| 三门县| 舞阳县| 得荣县| 合江县| 景洪市| 米林县| 龙陵县| 潼关县| 称多县| 汽车| 格尔木市| 华宁县| 通山县| 内黄县| 临沭县| 蒙阴县| 讷河市| 胶州市| 南陵县| 九龙城区| 广平县| 赞皇县| 保山市| 监利县| 揭阳市| 辽中县| 永仁县| 海林市| 香格里拉县| 凉城县| 会理县| 上饶市| 玉门市| 长葛市| 海盐县| 外汇| 绿春县| 卓资县|