1. 程式人生 > >【Java】重入鎖 實現原理

【Java】重入鎖 實現原理

ReentrantLock 是java繼synchronized關鍵字之後新出的執行緒鎖,今天看了看實現原始碼。主要是通過自旋來實現的。使用自旋的基本思路就是為所有的執行緒構建一個node,連成一個佇列,然後每一個node都輪詢前驅節點,如果前驅已經釋放鎖了,那麼當前階段就可以獲取鎖,否則就繼續迴圈。

之前瞭解過使用ThreadLocal機制實現的自旋鎖,但是reentrant lock應該是另一種。

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
這是加鎖方法,先進行一次快速加鎖,失敗了再進行常規加鎖。快速加鎖的情景指的是當前沒有鎖,所以直接CAS原子操作看看能不能獲取,也就是if塊裡的操作,如果沒有成功,常規獲取,也就是acquire操作。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
首先執行一次tryAcquire()嘗試獲取,分為公平和非公平,這裡就只看非公平的情況吧
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            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");
                setState(nextc);
                return true;
            }
            return false;
        }
首先獲取當前執行緒,然後得到鎖此時的state,如果state是0,說明可以爭取鎖,CAS一下,否則說明鎖被用了,但是如果用鎖的就是當前執行緒,就把state加1,獲取成功,否則就獲取失敗。

一旦tryAcquire()返回了false,說明獲取鎖失敗了,就必須進入等待佇列中,所以會執行後面的acquireQueued(addWaiter)方法。先看addWaiter

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
addWaiter所做的是把當前執行緒加入到等待佇列中,也就是把鎖的tail變數設定為當前執行緒,也是先快設定一次,也就是一次CAS,如果成功就返回,否則就執行一次常規加入,即enq操作
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
enq就是典型的自旋+CAS的實現,因為CAS控制併發是非阻塞的,所以如果一定要讓操作執行,必須把CAS放入迴圈內。所以enq就是一個while迴圈,不斷檢測CAS是否成功

一旦加入隊列了,剩下的就是執行acquireQueued方法,既然進入隊列了,為什麼還要執行這個方法?因為需要讓這個新加入的節點自旋,也就是讓其進入等待狀態,或者說讓它迴圈等待前驅階段是否釋放鎖

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
可以看到,確實有一個迴圈,不斷檢測前驅節點,如果前驅是head(這是一個dump頭節點),說明自己已經是真正的頭節點了,可以互獲鎖了,就會持續執行tryAcquire去競爭。這個佇列的消費是直接把消費的節點從佇列刪除,而之前部落格的CLH是通過節點的一個狀態字來檢測的。


可以看到,整個重入鎖就是通過自旋+CAS來實現的

獲取鎖的大致過程如下:

執行一次CAS獲取,若不成功則

執行一次tryAcquire獲取,若不成功則

把當前節點加入到佇列,也是先CAS加入一次,不成功再自旋

自旋檢測前驅是否釋放鎖,並嘗試獲取


與自旋鎖相對應的概念是互斥鎖,等待時是通過讓執行緒睡眠實現的。自旋鎖適用於佔用鎖時間比較短的場景,這樣執行緒狀態轉換的開銷相比較與cpu迴圈,代價會變大,但是隨著鎖獲取時間的增長,cpu的代價會越來越大


相關推薦

Java 實現原理

ReentrantLock 是java繼synchronized關鍵字之後新出的執行緒鎖,今天看了看實現原始碼。主要是通過自旋來實現的。使用自旋的基本思路就是為所有的執行緒構建一個node,連成一個佇列,然後每一個node都輪詢前驅節點,如果前驅已經釋放鎖了,那麼當前階段就

javaReentrantLock原理

ReentrantLock JUC中ReentrantLock實現了獨佔模式重入鎖,對於可重入,此類的註釋是這樣的: 當一個執行緒鎖住ReentrantLock,但是沒有解鎖,這個執行緒再執行加鎖方法會返回成功,並獲得這把鎖 Reentrant

Java手把手理解CAS實現原理

先來看看概念,【CAS】 全稱“CompareAndSwap”,中文翻譯即“比較並替換”。 定義:CAS操作包含三個運算元 —— 記憶體位置(V),期望值(A),和新值(B)。 如果記憶體位置的值與期望值匹配,那麼處理器會自動將該位置值更新為新值。否則, 處理器不作任何操作。無論哪種情況,它都會在C

java(ReentrantLock)的實現原理

聲明 其他人 不用 vol 喚醒 ola 是不是 結束 真的 前言相信學過java的人都知道 synchronized 這個關鍵詞,也知道它用於控制多線程對並發資源的安全訪問,興許,你還用過Lock相關的功能,但你可能從來沒有想過java中的鎖底層的機制是怎麽實現的。如果真

輕鬆學習java(ReentrantLock)的實現原理

前言 相信學過java的人都知道 synchronized 這個關鍵詞,也知道它用於控制多執行緒對併發資源的安全訪問,興許,你還用過Lock相關的功能,但你可能從來沒有想過java中的鎖底層的機制是怎麼實現的。如果真是這樣,而且你有興趣瞭解,今天我將帶領你輕鬆

漫畫圖解java(ReentrantLock)的實現原理

前言 相信學過java的人都知道 synchronized 這個關鍵詞,也知道它用於控制多執行緒對併發資源的安全訪問,興許,你還用過Lock相關的功能,但你可能從來沒有想過java中的鎖底層的機制是怎麼實現的。如果真是這樣,而且你有興趣瞭解,今天我將帶領你輕鬆的學習下j

Java使用Atomic變數實現

Atomic原子操作 Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程式設計師在多執行緒環境下,無鎖的進行原子操作。原子變數的底層使用了處理器提供的原子指令,但是不同的CPU架構可能提供的原子指令不一樣,也有可能需要某種形式的內部鎖,所

JavaSwing+IO流實現一個簡單的文件加密程序

als oncommand override fault 源文件 abs directory imp select EncrytService package com.my.service; import java.io.File; import java

JavaSwing+IO流實現一個簡單的文件加密程序(較完整版)

move 初始 baidu images 文件選擇器 while login 一個 ktr 留著參考 beans package com.my.bean; import java.io.Serializable; public class

淺談BloomFilter基本概念和實現原理

pty 是否 的人 它的 構建 網絡爬蟲 ace head filters ? ??在日常生活中。包括在設計計算機軟件時,我們常常要推斷一個元素是否在一個集合中。

Java載(Overload)與重寫(Override)

{} 方法 ide jump exce 上一條 內部 https pre 方法的語法 修飾符 返回值類型 方法名(參數類型 參數名){ ... 方法體 ... return 返回值; } 重載(overload) /** * 重載

springSpring aop的實現原理

本文轉載自https://www.cnblogs.com/lcngu/p/5339555.html。 Spring aop的實現原理 簡介   前段時間寫的java設計模式--代理模式,最近在看Spring Aop的時候,覺得於代理模式應該有密切的聯絡,於是決定了解下Sprin

Java模擬Sping,實現其IOC和AOP核心(一)

在這裡我要實現的是Spring的IOC和AOP的核心,而且有關IOC的實現,註解+XML能混合使用! 參考資料: IOC:控制反轉(Inversion of Control,縮寫為IoC),是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入(D

JavaReentrantLock

在JDK1.5之前,我們設計程式進行執行緒之間通訊時必須使用同步鎖,獲得同步鎖必須使用同步關鍵字synchronized(lock)的形式。JDK1.5提供了執行緒同步相關的包java.util.concurrent,引入了可重入鎖ReentrantLock,使用起來很方便並且提高了程式碼執

Java與其釋放

在學習Java多執行緒相關的知識時,通常都會涉及鎖這個概念,常見的synchronized、Lock均為可重入鎖。為了更好的理解可重入鎖,需要先理解一下幾個問題: 1、誰持有了鎖?   2、鎖的物件是誰? 3、可重入鎖的可重入是什麼意思? 一、synchronized關鍵字

java基礎總結(二十九)--Java不可和可理解

來自:https://blog.csdn.net/u012545728/article/details/80843595   最近正在閱讀Java ReentrantLock原始碼,始終對可重入和不可重入概念理解不透徹,進行學習後記錄在這裡。 基礎知識 Java多執行緒的wai

JavaScriptFunction.prototype.bind實現原理

前言:ECMAscript5中的bind()是ES6中箭頭函式繫結this的基礎。它是的實現原理是怎樣的呢? 一、回顧bind的用法 const nick = { name:'nick'

Java基於jsoup爬蟲實現(從智聯獲取工作資訊)

這幾天在學習Java解析xml,突然想到Dom能不能解析html,結果試了半天行不通,然後就去查了一些資料,發現很多人都在用Jsoup解析html檔案,然後研究了一下,寫了一個簡單的例項,感覺還有很多地方需要潤色,在這裡分享一下我的例項,歡迎交流指教!後續想通過Java把資料匯入到Excel或者

Java模擬Sping,實現其IOC和AOP核心(二)

接著上一篇,在上一篇完成了有關IOC的註解實現,這一篇用XML的方式實現IOC,並且完成AOP。 簡易的IOC框圖 註解的方式實現了左邊的分支,那麼就剩下右邊的XML分支: XmlContext:這個類是也是AbstractApplicationContext的子類,和AnnotationContext

JAVASrping和JDBC實現資料庫操作

前言 建立資料庫 首先建立我們的資料庫(這裡我使用的是Mysql),為了演示方便,我這裡簡單的建立一個spring資料庫,然後資料庫有一個user使用者表: 建立一個名為spring的資料庫。 建立一個名為user的資料表,表包括id、email、name