1. 程式人生 > >ReentrantLock重入鎖

ReentrantLock重入鎖

fields extend 維護 else ole sse all monitors base

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

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

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


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得構造方法

    /**
     * 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()實現方法得差異。下面我們就來一一介紹,首先介紹FairSync

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)這個方法。同樣,看代碼。

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。

然後我們再回歸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。

然後再回歸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來實現循環打印數字及字母。

ReentrantLock重入鎖