国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

【java并發(fā)編程實戰(zhàn)6】AQS之獨占鎖ReentrantLock實現(xiàn)

sixleaves / 2581人閱讀

摘要:鎖與很好的隔離使用者與實現(xiàn)者所需要關注的領域。那么這個就是包裝線程并且放入到隊列的過程實現(xiàn)的方法。也證實了就是獲取鎖的線程的節(jié)點。如果發(fā)生異常取消請求,也就是將當前節(jié)點重隊列中移除。

前言

自從JDK1.5后,jdk新增一個并發(fā)工具包java.util.concurrent,提供了一系列的并發(fā)工具類。而今天我們需要學習的是java.util.concurrent.lock也就是它下面的lock包,其中有一個最為常見類ReentrantLock,

我們知道ReentrantLock的功能是實現(xiàn)代碼段的并發(fā)訪問控制,也就是通常意義上所說的鎖。之前我們也學習過一種鎖的實現(xiàn),也就是synchronized關鍵詞,synchronized是在字節(jié)碼層面,通過對象的監(jiān)視器鎖實現(xiàn)的。那么ReentrantLock又是怎么實現(xiàn)的呢?

如果不看源碼,可能會以為它的實現(xiàn)是通過類似于synchronized,通過對象的監(jiān)視器鎖實現(xiàn)的。但事實上它僅僅是一個工具類!沒有使用更“高級”的機器指令,不是關鍵字,也不依靠JDK編譯時的特殊處理,僅僅作為一個普普通通的類就完成了代碼塊的并發(fā)訪問控制,這就更讓人疑問它怎么實現(xiàn)的代碼塊的并發(fā)訪問控制的了。

我們查看源碼發(fā)現(xiàn),它是通過繼承抽象類實現(xiàn)的AbstractQueuedSynchronizer,為了方便描述,接下來我將用AQS代替AbstractQueuedSynchronizer

關于AQS
AQS,它是用來構建鎖或者其他同步組建的基礎框架,我們見過許多同步工具類都是基于它構建的。包括ReentrantLock、CountDownLatch等。在深入了解AQS了解之前,我們需要知道鎖跟AQS的區(qū)別。鎖,它是面向使用者的,它定義了使用者與鎖交互的接口,隱藏了實現(xiàn)的細節(jié);而AQS面像的是鎖的實現(xiàn)者,它簡化了鎖的實現(xiàn)。鎖與AQS很好的隔離使用者與實現(xiàn)者所需要關注的領域。那么我們今天就作為一個鎖的實現(xiàn)者,一步一步分析鎖的實現(xiàn)。

AQS又稱同步器,它的內部有一個int成員變量state表示同步狀態(tài),還有一個內置的FIFO隊列來實現(xiàn)資源獲取線程的排隊工作。通過它們我們就能實現(xiàn)鎖。

在實現(xiàn)鎖之前,我們需要考慮做為鎖的使用者,鎖會有哪幾種?

通常來說,鎖分為兩種,一種是獨占鎖(排它鎖,互斥鎖),另一種就是共享鎖了。根據(jù)這兩類,其實AQS也給我們提供了兩套API。而我們作為鎖的實現(xiàn)者,通常都是要么全部實現(xiàn)它的獨占api,要么實現(xiàn)它的共享api,而不會出現(xiàn)一起實現(xiàn)的。即使juc內置的ReentrantReadWriteLock也是通過兩個子類分別來實現(xiàn)的。

鎖的實現(xiàn) 獨占鎖

獨占鎖又名互斥鎖,同一時間,只有一個線程能獲取到鎖,其余的線程都會被阻塞等待。其中我們常用的ReentrantLock就是一種獨占鎖,我們一起來ReentrantLock 分析ReentrantLock的同時看一看AQS的實現(xiàn),再推理出AQS獨特的設計思路和實現(xiàn)方式。最后,再看其共享控制功能的實現(xiàn)。

首先我們來看看獲取鎖的過程

加鎖

我們查看ReentrantLock的源碼。來分析它的lock方法

  public void lock() {
        sync.lock();
   }

與我們之前分析的一樣,鎖的具體實現(xiàn)由內部的代理類完成,lock只是暴露給鎖的使用者的一套api。使用過ReentrantLock的同學應該知道,ReentrantLock又分為公平鎖和非公平鎖,所以,ReentrantLock內部只有兩個sync的實現(xiàn)。

    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync{..}
     /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync{..}

公平鎖 :每個線程獲取鎖的順序是按照調用lock方法的先后順序來的。

非公平鎖:每個線程獲取鎖的順序是不會按照調用lock方法的先后順序來的。完全看運氣。

所以我們完全可以猜測到,這個公平與不公平的區(qū)別就體現(xiàn)在鎖的獲取過程。我們以公平鎖為例,來分析獲取鎖過程,最后對比非公平鎖的過程,尋找差異。

lock

查看FairSync的lock方法

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

這里它調用到了父類AQS的acquire方法,所以我們繼續(xù)查看acquire方法的代碼

acquire
/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
            selfInterrupt();
    }

查看方法方法的注釋我們可以知道這個方法的作用,這里我簡單的翻譯一下.

Acquires方法是一個獨占鎖模式的方法,它是不會響應中斷的。它至少執(zhí)行一次tryAcquire去獲取鎖,如果返回true,則代表獲取鎖成功,否則它將會被加入等待隊列阻塞,直到重新嘗試獲取鎖成功。所以我們需要看看嘗試獲取鎖的方法tryAcquire的實現(xiàn)

tryAcruire
   protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

拋出一個異常,沒有實現(xiàn)。所以我們需要查看它的子類,在我們這里就是FairSync的實現(xiàn)。

這里也會大家會有疑惑,沒有實現(xiàn)為什么不寫成抽象方法呢,前面我們提到過,我們不會同時在一個類中實現(xiàn)獨占鎖跟共享鎖的api,那么tryAcruire是屬于獨占鎖,那么如果我想一個共享鎖也要重新獨占鎖的方法嗎?所以大師的設計是絕對沒有問題的。
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當前線程
            int c = getState();  //獲取父類AQS中的標志位
            if (c == 0) {
                if (!hasQueuedPredecessors() && 
                    //如果隊列中沒有其他線程  說明沒有線程正在占有鎖!
                    compareAndSetState(0, acquires)) { 
                    //修改一下狀態(tài)位,注意:這里的acquires是在lock的時候傳遞來的,從上面的圖中可以知道,這個值是寫死的1
                    setExclusiveOwnerThread(current);
                    //如果通過CAS操作將狀態(tài)為更新成功則代表當前線程獲取鎖,因此,將當前線程設置到AQS的一個變量中,說明這個線程拿走了鎖。
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
             //如果不為0 意味著,鎖已經(jīng)被拿走了,但是,因為ReentrantLock是重入鎖,
             //是可以重復lock,unlock的,只要成對出現(xiàn)行。一次。這里還要再判斷一次 獲取鎖的線程是不是當前請求鎖的線程。
                int nextc = c + acquires;//如果是的,累加在state字段上就可以了。
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

目前為止,如果獲取鎖成功,則返回true,獲取鎖的過程結束,如果獲取失敗,則返回false

按照之前的邏輯,如果線程獲取鎖失敗,則會被放入到隊列中,但是在放入之前,需要給線程包裝一下。

那么這個addWaiter就是包裝線程并且放入到隊列的過程實現(xiàn)的方法。

addWaiter
   /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    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;
    }

注釋: 把當前線程作為一個節(jié)點添加到隊列中,并且為這個節(jié)點設置模式

模式: 也就是獨占模式/共享模式,在這里模式是形參,所以我們看看起調方

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) Node.EXCLUSIVE 就代表這是獨占鎖模式。

創(chuàng)建好節(jié)點后,將節(jié)點加入到隊列尾部,此處,在隊列不為空的時候,先嘗試通過cas方式修改尾節(jié)點為最新的節(jié)點,如果修改失敗,意味著有并發(fā),這個時候才會進入enq中死循環(huán),“自旋”方式修改。

將線程的節(jié)點接入到隊里中后,當然還需要做一件事:將當前線程掛起!這個事,由acquireQueued來做。

在解釋acquireQueued之前,我們需要先看下AQS中隊列的內存結構,我們知道,隊列由Node類型的節(jié)點組成,其中至少有兩個變量,一個封裝線程,一個封裝節(jié)點類型。

而實際上,它的內存結構是這樣的(第一次節(jié)點插入時,第一個節(jié)點是一個空節(jié)點,代表有一個線程已經(jīng)獲取鎖,事實上,隊列的第一個節(jié)點就是代表持有鎖的節(jié)點):

黃色節(jié)點為隊列默認的頭節(jié)點,每次有線程競爭失敗,進入隊列后其實都是插入到隊列的尾節(jié)點(tail后面)后面。這個從enq方法可以看出來,上文中有提到enq方法為將節(jié)點插入隊列的方法:

enq
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                // 一個空的節(jié)點,通常代表獲取鎖的線程
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
acquireQueued

接著我們來看看當節(jié)點被放入到隊列中,如何將線程掛起,也就是看看acquireQueued方法的實現(xiàn)。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 獲取當前節(jié)點前驅結點
                final Node p = node.predecessor();
                // 如果前驅節(jié)點是head,那么它就是等待隊列中的第一個線程
                // 因為我們知道head就是獲取線程的節(jié)點,那么它就有機會再次獲取鎖
                if (p == head && tryAcquire(arg)) {
                    //成功后,將上圖中的黃色節(jié)點移除,Node1變成頭節(jié)點。 也證實了head就是獲取鎖的線程的節(jié)點。
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 1、檢查前一個節(jié)點的狀態(tài),判斷是否要掛起
                // 2、如果需要掛起,則通過JUC下的LockSopport類的靜態(tài)方法park掛起當前線程,直到被喚醒。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 如果發(fā)生異常
            if (failed)
                // 取消請求,也就是將當前節(jié)點重隊列中移除。
                cancelAcquire(node);
        }
    }

這里我還需要解釋的是:

1、Node節(jié)點除了存儲當前線程之外,節(jié)點類型,前驅后驅指針之后,還存儲一個叫waitStatus的變量,該變量用于描述節(jié)點的狀態(tài)。共有四種狀態(tài)。

       /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor"s thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

分別表示:

1 = 取消狀態(tài),該節(jié)點將會被隊列移除。

-1 = 等待狀態(tài),后驅節(jié)點處于等待狀態(tài)。

-2 = 等待被通知,該節(jié)點將會阻塞至被該鎖的condition的await方法喚醒。

-3 = 共享傳播狀態(tài),代表該節(jié)點的狀態(tài)會向后傳播。

到此為止,一個線程對于鎖的一次競爭才告于段落,結果有兩種,要么成功獲取到鎖(不用進入到AQS隊列中),要么,獲取失敗,被掛起,等待下次喚醒后繼續(xù)循環(huán)嘗試獲取鎖,值得注意的是,AQS的隊列為FIFO隊列,所以,每次被CPU假喚醒,且當前線程不是出在頭節(jié)點的位置,也是會被掛起的。AQS通過這樣的方式,實現(xiàn)了競爭的排隊策略。

釋放鎖

看完了加鎖,再看釋放鎖。我們先不看代碼也可以猜測到釋放鎖需要的步驟。

隊列的頭節(jié)點是當前獲取鎖的線程,所以我們需要移除頭節(jié)點

釋放鎖,喚醒頭節(jié)點后驅節(jié)點來競爭鎖

接下來我們查看源碼來驗證我們的猜想是否在正確。

unlock
public void unlock() {
    sync.release(1);
}

unlock方法調用AQS的release方法,因為我們的acquire的時候傳入的是1,也就是同步狀態(tài)量+1,那么對應的解鎖就要-1。

release
  public final boolean release(int arg) {
        // 嘗試釋放鎖
        if (tryRelease(arg)) {
            // 釋放鎖成功,獲取當前隊列的頭節(jié)點
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 喚醒當前節(jié)點的下一個節(jié)點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
tryRelease

同樣的它是交給子類實現(xiàn)的

    protected final boolean tryRelease(int releases) {
        
            int c = getState() - releases;
            // 當前線程不是獲取鎖的線程 拋出異常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 因為是重入的關系,不是每次釋放鎖c都等于0,直到最后一次釋放鎖時,才通知AQS不需要再記錄哪個線程正在獲取鎖。
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
    }
unparkSuccessor

釋放鎖成功之后,就喚醒頭節(jié)點后驅節(jié)點來競爭鎖

 private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

值得注意的是,尋找的順序是從隊列尾部開始往前去找的最前面的一個waitStatus小于0的節(jié)點。因為大于0 就是1狀態(tài)的節(jié)點是取消狀態(tài)。

公平鎖與非公平鎖

到此我們鎖獲取跟鎖的釋放已經(jīng)分析的差不多。那么公平鎖跟非公平鎖的區(qū)別在于加鎖的過程。對比代碼

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }
}
static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 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修改一次state變量(嘗試獲取鎖),成功就返回,不成功再排隊,從而達到不排隊直接搶占的目的。

最后歡迎大家關注一下我的個人公眾號。一起交流一起學習,有問必答。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77100.html

相關文章

  • BATJ都愛問的多線程面試題

    摘要:今天給大家總結一下,面試中出鏡率很高的幾個多線程面試題,希望對大家學習和面試都能有所幫助。指令重排在單線程環(huán)境下不會出先問題,但是在多線程環(huán)境下會導致一個線程獲得還沒有初始化的實例。使用可以禁止的指令重排,保證在多線程環(huán)境下也能正常運行。 下面最近發(fā)的一些并發(fā)編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發(fā)編程問題就沒有那么頭疼了。今天給大家總結一下,面試中出鏡率很高的幾個多線...

    高勝山 評論0 收藏0
  • Java 重入 ReentrantLock 原理分析

    摘要:的主要功能和關鍵字一致,均是用于多線程的同步。而僅支持通過查詢當前線程是否持有鎖。由于和使用的是同一把可重入鎖,所以線程可以進入方法,并再次獲得鎖,而不會被阻塞住。公平與非公平公平與非公平指的是線程獲取鎖的方式。 1.簡介 可重入鎖ReentrantLock自 JDK 1.5 被引入,功能上與synchronized關鍵字類似。所謂的可重入是指,線程可對同一把鎖進行重復加鎖,而不會被阻...

    lx1036 評論0 收藏0
  • Java多線程進階(七)—— J.U.Clocks框架:AQS獨占功能剖析(2)

    摘要:開始獲取鎖終于輪到出場了,的調用過程和完全一樣,同樣拿不到鎖,然后加入到等待隊列隊尾然后,在阻塞前需要把前驅結點的狀態(tài)置為,以確保將來可以被喚醒至此,的執(zhí)行也暫告一段落了安心得在等待隊列中睡覺。 showImg(https://segmentfault.com/img/remote/1460000016012467); 本文首發(fā)于一世流云的專欄:https://segmentfault...

    JayChen 評論0 收藏0
  • Java,真的有這么復雜嗎?

    摘要:撤銷鎖偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖。輕量級鎖線程在執(zhí)行同步代碼塊之前,會先在當前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的復制到鎖記錄中,官方稱為。 前言 作者前面也寫了幾篇關于Java并發(fā)編程,以及線程和volatil的基礎知識,有興趣可以閱讀作者的原文博客,今天關于Java中的兩種鎖進行詳解,希望對...

    Darkgel 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<