ReentrantLock那些事

前言

在說ReentrantLock之前,我們先說說併發吧。

在JDK1.5之前,併發處理常用的關鍵字synchronized。使用synchronized關鍵字,鎖的獲取和釋放是隱式的,synchronized主要通過系統的monitorenter指令實現的。

那時候synchronized可以稱爲重量級鎖,執行效率不是很高。

而Doug Lea編寫的util.concurrent 包被納入JSR-166標準。這裏面就包含了ReentrantLock。

ReentrantLock爲編寫併發提供了更多選擇。

使用

ReentrantLock的通常用法如下:

 public class X {
    private final ReentrantLock lock = new ReentrantLock();

    public void m() {
      lock.lock();  
      try {
        //TODO
      } finally {
        lock.unlock();
      }
    }
  }

原理

ReentrantLock主要是通過AbstractQueuedSynchronizer實現的,是一個重入鎖,即一個線程加鎖後仍然可以獲得鎖,不會出現自己阻塞自己的情況。

UML圖

我們看一下它們的UML圖。

這裏寫圖片描述

可以看到ReentrantLock實現了Lock接口。

鎖類型

ReentrantLock的兩種鎖類型,公平鎖和非公平鎖。

這裏寫圖片描述

這裏寫圖片描述

源碼分析

我們先來看下ReentrantLock的構造方法。

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可以看到默認無參構造方法爲非公平鎖實現。如果想定義公平鎖實現,可以傳入true來控制。

它的lock方法:

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

公平鎖和非公平鎖各有自己的實現方式。我們來看下他們的tryAcquire方法。

非公平鎖源碼:

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    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;
        }

可以看到,非公平鎖首先判斷AQS(AbstractQueuedSynchronizer)中的state是否爲0,0表示沒有線程持有該鎖,當前線程就嘗試獲取鎖。

如果不是0,那在判斷是不是當前線程持有該鎖,如果是,就會增加state,改變state狀態。(因此ReentranLock支持重入)。

公平鎖源碼:

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        @ReservedStackAccess
        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;
        }
    }
    public final boolean hasQueuedPredecessors() {
        Node t = tail; 
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

公平鎖的tryAcquire方法,可以看到,相比非公平鎖,多了hasQueuedPredecessors方法,這個方法是判斷隊列中是否有其他線程,如果沒有,線程纔會嘗試獲取鎖,如果有,會先把鎖分配給隊列的線程,因此稱爲公平鎖。

這兒可以看到,非公平鎖的效率比公平鎖要高。

這是tryAcquire方法,如果嘗試獲取鎖失敗了呢?

那就會執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法啦。

我們先來看一下addWaiter方法。

    private Node addWaiter(Node mode) {
        Node node = new Node(mode);

        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }

可以看到,這個方法會把線程添加到隊列尾,同時,for(;;)循環保證添加成功,直到return出去。

添加後,調用acquireQueued方法,這個方法爲掛起等待線程。

看下該方法源碼:

    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

可以看到,如果節點爲頭節點,就嘗試獲取一次鎖,如果成功,就返回。

否則判斷該線程是否需要掛起,如果需要的化就調用parkAndCheckInterrupt掛起。

調用LockSupport.park方法掛起線程,直到被喚醒。

selfInterrupt方法:

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }
    public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();
        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

調用interrupt方法,中斷正在執行的線程(如果不是當前線程的話)。

釋放鎖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;
    }
    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;
        }

可以看到首先會判斷當前線程是否是獲得鎖的線程,如果是重入鎖需要將state減完纔算是完全釋放鎖。

釋放後調用unparkSuccessor喚起掛起線程。

總結

  1. 非公平鎖的效率是比公平鎖要高的。
  2. ReentranLock支持重入,因爲增加了對自身線程的處理,通過state可以控制。
  3. 解鎖操作應放到finally塊裏,避免使用鎖時出現資源無法釋放的問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章