ReentrantLock中lock/trylock/lockInterruptibly方法的區別及源碼解析

看了幾篇關於這三者區別的文章,但都說的不夠具體,自己去讀了下源碼,大概是清楚了三者的功能,不想了解源碼的可以跳到最後看總結。

首先,ReentrantLock類中使用了大量的CAS操作,也就是CompareAndSwap原子操作,依靠硬件保證互斥與同步,然後說下interrupt()方法。每個線程都有一個interrupt標誌。當線程在阻塞狀態時,如sleep、wait、await(park)、join,此時如果對該進程調用interrupt()方法,該線程會被喚醒、interrupt標誌被修改,並拋出InterruptedException異常(一旦捕捉到異常,立刻重置interrupt標誌),因此上述的那些方法被要求爲必須捕捉異常;當線程在運行中並未進入任何阻塞狀態時,則interrupt()操作不會打算線程執行,只會修改該線程interrupt標誌,此時可以通過Interrupted()/isInterrupted()查看並作出處理(前者會重置該標誌位),通常使用while循環來檢查。

一、 lock()方法

使用lock()獲取鎖,若獲取成功,標記下是該線程獲取到了鎖用於鎖重入),然後返回。若獲取失敗,這時跑一個for循環,循環中先將線程阻塞放入等待隊列,當被調用signal()時線程被喚醒,這時進行鎖競爭(因爲默認使用的是非公平鎖),如果此時用CAS獲取到了鎖那麼就返回,如果沒獲取到那麼再次放入等待隊列,等待喚醒,如此循環。其間就算外部調用了interrupt(),循環也會繼續走下去。一直到當前線程獲取到了這個鎖,此時才處理interrupt標誌,若有,則執行 Thread.currentThread().interrupt(),結果如何取決於外層的處理。lock()最終執行的方法如下:

    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; //獲取成功返回interrupted標誌
               }
                 // 只修改標誌位,不做其他處理
                 if (shouldParkAfterFailedAcquire(p, node) && <strong>parkAndCheckInterrupt()</strong>) 
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

其中parkAndCheckInterrupt()調用了LockSupport.park(),該方法使用Unsafe類將進程阻塞並放入等待隊列,等待喚醒,和await()有點類似。

可以看到循環中檢測到了interrupt標記,但是僅做 interrupted = true 操作,直到獲取到了鎖,才return interrupted,然後處理如下

   public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt(); // 執行Thread.currentThread().interrupt()
    }

二、 lockInterruptibly()方法

和lock()相比,lockInterruptibly()只有略微的修改,for循環過程中,如果檢測到interrupt標誌爲true,則立刻拋出InterruptedException異常,這時程序變通過異常直接返回到最外層了,又外層繼續處理,因此使用lockInterruptibly()時必須捕捉異常。lockInterruptibly()最終執行的方法如下:

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return; //獲取成功返回
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException(); //直接拋出異常
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

三、 tryLock()方法

使用tryLock()嘗試獲取鎖,若獲取成功,標記下是該線程獲取到了鎖,然後返回true;若獲取失敗,此時直接返回false,告訴外層沒有獲取到鎖,之後的操作取決於外層,代碼如下:

        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;
        }
else if 中的情況屬於鎖重入情況,即外層函數已經獲取到這個鎖了,內部的函數又再次對其進行獲取,那麼對計數器加一,不再循環加鎖。

還有一種情況,即使用tryLock(long timeout, TimeUnit unit)獲取鎖,此時有點像lock()和lockInterruptibly()的混合體,若獲鎖取成功,直接返回true;若獲取失敗跑一個for循環,則用 LockSupport.parkNanos(this, nanosTimeout) 阻塞進程並放入等待隊列,等待喚醒,同時timeout扣減每次循環消耗的時間,當timeout用盡時如果依然沒有獲取到鎖,那麼就返回false。對於循環期間收到的interrupt()的處理,這兒和lockInterruptibly()一樣,一旦檢測到,那麼直接拋出異常。代碼如下:

    private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
        long lastTime = System.nanoTime();
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true; //獲取成功返回
                }
                if (nanosTimeout <= 0)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
                    <strong>LockSupport.parkNanos(this, nanosTimeout)</strong>; //帶timeout的阻塞
                long now = System.nanoTime();
                nanosTimeout -= now - lastTime;
                lastTime = now; // 更新timeout
                if (Thread.interrupted())
                    throw new InterruptedException(); //直接拋出異常
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }

總結

  1. lock()  阻塞式地獲取鎖,只有在獲取到鎖後才處理interrupt信息
  2. lockInterruptibly() 阻塞式地獲取鎖,立即處理interrupt信息,並拋出異常
  3. tryLock()  嘗試獲取鎖,不管成功失敗,都立即返回true、false
  4. tryLock(long timeout, TimeUnit unit)在timeout時間內阻塞式地獲取鎖,成功返回true,超時返回false,同時立即處理interrupt信息,並拋出異常

以上就是我對這三種方法的認識,第一次寫技術博客,發現還是挺耗時間的,如果有什麼錯誤還望大家請指正。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章