天天看點

ReentrantLock實作思路分析

ReentrantLock是依賴CAS機制寫的。

CAS全名Compare And Swap,比較如果相同就交換,全名更容易記住,并且具有意義。

CAS在ReentrantLock中的實作,樣例1:

//同步器狀态,如果是0,同步器中沒有線程在運作,也就是lock釋放狀态
    //如果是1,則代表同步器中有線程在運作,lock沒有被釋放
    private volatile int state;  

    //擷取state變量在目前對象所占記憶體裡的偏移量(就是state的位置)
    private static final long stateOffset;
    stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));

    //調用這個方法來更新state值
    //expect:期望值
    //update:更新值
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
           

變量一定要volatile修飾,保證變量的可見性。對volatile的簡要說明:ReentrantLock實作就是用來保證多線程的并發資料統一性的安全控制,在多線程下,要修改一個公共變量,可能會因為更新不及時其他線程導緻髒讀,而volatile就是能保證各個線程能及時看到操作後最新的值。(volatile的實作原理涉及比較底層了)。

votaile修飾完變量,為了在多線程情況下,能原子性的進行更新操作。這裡的原子性操作隻有兩步,首先比較值,然後更新。

拿上面的這行代碼unsafe.compareAndSwapInt(this, stateOffset, expect, update);來說,this代表目前對象,stateOffset代表變量的位置,expect代表此變量我們期望的值,update代表此變量将要更新的值。實際場景:如果此變量現在的值和我們預期的一樣,那麼就更新值。

了解完CAS,再來講一下ReentrantLock大概結構:

ReentrantLock它繼承了AbstractQueuedSynchronizer抽象類,當你看到ReentrantLock是依賴CAS和AQS的時候,不要疑惑AQS是啥,就是AbstractQueuedSynchronizer,AQS是一個很重要的類。java中實作的很多鎖的重要實作都依賴這個類。

AQS的實作大概思路:想象一下在多線程環境下,我怎麼控制它們一個個執行呢。首先,我們要把這些要執行的線程給存儲起來,這個存儲器就叫它同步器吧。AQS采用的是雙向連結清單結構去存儲。定義一個Node類,主要變量包括Thread和mode,Thread當然是線程,mode是模式類别(獨占式和共享式),ReentrantLock就是獨占式模式,因為鎖一次隻能被一個線程擷取。存儲好了,那麼如何控制這些線程一個個運作呢?ReentrantLock的控制就是根據上面這個state變量來實作的。

我們來嘗試解讀一下ReentrantLock中的非公平鎖裡的lock()方法:

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

首先判斷compareAndSetState(0, 1)是否執行成功,執行成功的條件就是期望state=0,代表這時候同步器裡沒有線程在運作,鎖是空閑的。我們把state設定為1了,代表鎖被占用了,其他的線程執行lock()方法時就會被挂起。擷取到了鎖,再執行setExclusiveOwnerThread(Thread.currentThread());這個是将目前線程給變量指派操作,存儲是哪個線程擷取到了鎖。如果沒有成功則執行acquire(1)。

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

這裡tryAcquire(arg)方法仍然嘗試去擷取鎖,如果不成功,再往下執行。先執行addWaiter(Node.EXCLUSIVE),這個是将目前線程設定為獨占式節點插入到同步器的末端(将目前線程存儲起來)。再執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法

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);
        }
    }
           

可以看到這裡是一個for(;;)無限循環,一直嘗試去擷取鎖,那麼這不是死循環了嗎。parkAndCheckInterrupt()會調動Unsafe的unPark(false,0L)方法,相當于sleep(0),讓目前線程放棄cpu,讓cpu可以去做其他任務,這樣完成一個為搶占鎖不斷阻塞的程式。關于sleep(0),這篇文章寫的不錯:https://blog.csdn.net/qiaoquan3/article/details/56281092/

具體情形還請自尋檢視源碼。ReentrantLock的unlock()方法比它lock()方法簡單多了。

繼續閱讀