ReentrantLock重入鎖

  上次博客講到了經過wait()方法和notify()方法來實現循環打印數字和字母得問題。其實使用重入鎖也能夠實現一樣得功能,那麼開始咱們先經過源碼來了解一下重入鎖把。java

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

首先它有一個lock()方法,它用來加鎖,從代碼中能夠看到,它調用得是sync.lock()方法,node

 
 
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;

/**
* Base of synchronization control for this lock. Subclassed
* into fair and nonfair versions below. Uses AQS state to
* represent the number of holds on the lock.
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;

/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
 

在這個類裏面,有一個靜態抽象類Sync對象以及Sync得屬性,所以咱們能夠知道它調用得是Sync裏面得lock()方法,而Sync又是一個抽象類,lock()方法也是一個抽象方法,具體由它得子類去實現。安全

而後咱們接着看ReentrantLock得構造方法ui

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

經過代碼能夠看到,在初始化得時候會初始化Sync對象,經過代碼能夠看出若是不帶參數得話默認使用得是NonfairSync這個子類,也能夠指定使用FairSync這個子類。好了,經過以上代碼咱們能夠知道,ReentrantLock這個類會在實例化得時候指定FairSync或者NonFairSync,下面咱們來介紹一下這兩個類。首先經過字面意思能夠看出前者得意思是「公平鎖」,後者得意思是「非公平鎖」。那麼爲何要這麼叫呢,實際上是由於他們得lock()實現方法得差別。下面咱們就來一一介紹,首先介紹FairSyncthis

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

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

從代碼能夠看出最後得實現是acquire(1)這個方法,那麼這個方法幹了什麼呢?看代碼其實挺少得,其實幹得事情並很多。首先會執行tryAcquire(arg)這個方法。一樣,看代碼。spa

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

咱們一步步來看,首先獲取當前得線程,而後或者狀態c,這個c很重要,它得含義就是這個「重入」得含義,等會再說。getState()方法經過代碼能夠看出返回得是ReentrantLock裏面得state屬性,由於是int類型,因此默認爲0,表明沒有線程正在使用它,這裏講了c這個變量得含義,咱們接着往下看,若是c等於0,也就是說沒有線程正在使用,那麼他會進入下一個if判斷,首先會執行hasQueuedPredecessors()方法。一樣繼續點進去看代碼實現線程

public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

這個方法是AbstractQueuedSynchronizer這個類裏面得方法,經過代碼能夠看出這個類裏面維護了一個FIFO隊列,隊列中每個元素都是一個Node節點,其中Node對象有prev屬性用來表示前一個節點,next屬性用來表示下一個節點,thread屬性用來標識當前線程,隊列有一個head(頭)節點和tail(尾)節點,head節點僅保存下一個節點得引用。就簡單介紹這麼點,由於這些是用來幫助咱們理解上面代碼得含義得。下面我用中文描述一下這個判斷幹了什麼事情,返回 頭節點 != 尾節點而且(頭節點得下一個節點爲空或者頭節點得下一個節點得線程不等於當前線程),這麼說有一點繞,其實咱們看方法名能夠知道這個方法是用來判斷是否存在等待着得對象想要得到鎖,首先假設隊列爲空,那麼頭節點等於尾節點,返回false,若是頭節點不等於尾節點,那麼頭節點得下一個節點確定不爲空,而後判斷頭節點得下一個節點得線程是否是當前線程,若是是,返回false,若是不是,返回true。code

而後咱們再回歸tryAcquire()方法。若是沒有等着着得線程。那麼它會執行compareAndSetState()方法。這個方法得底層是經過CAS來實現得,這裏簡單得介紹一下CAS,CAS是一種使用無鎖得方式來實現線程安全得方法,這個方法有三個參數,一個是要更新得遍歷V,一個是預期值E,一個是新值N,若是V == E,那麼更新V爲N,若是V != E,那麼證實有其餘線程更改了這個變量,這個方法不會作任何事情,你能夠再從新執行這個方法或者選擇放棄。主要流程就是這個,有興趣得能夠去了解一下CAS。若是操做成功,設置當前線程爲正在使用得線程,返回true。這裏講解得是c等於0得狀況,若是c不等於0呢?判斷得到鎖得線程是否是當前線程,若是是,c加1,看到這裏其實也就明白了重入鎖這個詞是怎麼來的了,它能夠一直調用lock方法來加鎖,每調用一次,state加1。orm

而後再回歸acquire()方法。若是tryAcquire()方法獲取鎖成功,那麼不會執行其餘操做,若是失敗,會執行acquireQueued(addWaiter(Node.EXCLUSIVE), args)這個方法,而且當前線程阻塞。那麼這個方法又幹了什麼呢?先來看addWaiter(Node.EXCLUSIVE)這個方法,對象

static final Node EXCLUSIVE = null;
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;
    }

這個方法判斷隊列是否爲空,若是爲空,執行enq()方法,不爲空,執行上面得方法,最終得結果都是將當前線程假如到等待隊列中。而後acquireQueued()這個方法一層一層得,我沒有看太懂,這裏也就不說太多,功能其實就是讓等待隊列前面得獲取鎖。好了,整個FairSync的lock()方法已經介紹完了,那麼NonFairSync的lock()方法有什麼區別呢?

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

從代碼中能夠看到,它不會去管等待隊列什麼的,而是直接執行CAS操做,若是失敗了,好吧,失敗了大不了我就用FairSync的那一套咯。

而後咱們再來看看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;
    }

從代碼能夠看出,會執行tryRelease()方法,若是成功,而且等待隊列不爲空的話,喚醒隊列中第一個等待的線程。

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

由於重入鎖能夠屢次加鎖,所以只有當c爲0時,才能返回true。不然,返回false。也就是說,lock幾回就要unlock幾回才能釋放鎖。

整個ReentrantLock主要的就介紹完了,這些東西是經過查看源碼以及其餘的博客整理出來的,整個代碼的講解也是我本身的理解,可能語言組織方面不是太好。但願本身的講述不是太差勁。。。這篇主要整理了ReetrantLock的原理,下篇博客我準備使用ReentrantLock來實現循環打印數字及字母。

相關文章
相關標籤/搜索