今天看啥  ›  专栏  ›  有一首歌叫瓜牛

ReentrantLock源码解析--加锁过程

有一首歌叫瓜牛  · 掘金  ·  · 2020-06-12 02:59
阅读 21

ReentrantLock源码解析--加锁过程

公平锁的加锁过程

首先ReentrantLock技能是公平锁,又能是非公平锁,这里先讨论公平锁的加锁过程

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock(true);
    reentrantLock.lock();
}
复制代码

当我们使用带参数的造器生成ReentrantLock时,由于传入是true所以会生成一个公平锁的内部类对象。

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

无论是公平锁还是非公平锁都继承了ReentrantLock中Sync这个静态内部类对象,而这个对象又继承了AbstractQueuedSynchronizer(以下简称AQS),所以要分析ReenTrantLock的加锁原理,就要分析抽象类AbstractQueuedSynchronizer中的部分方法。现在我们从方法的调用开始逐行的分析里面的实现。

当我们调用reentrantLock.lock时,其实会调用具体锁的lock方法,当然这里指的是公平锁。这个方法是抽象类AQS需要具体类实现的方法。

public void lock() {
    sync.lock();
}
复制代码

然后公平锁的lock方法又调用acquire方法,并且穿了一个int类型的参数1,这个参数含义在下文分析

  final void lock() {
    acquire(1);
}
复制代码

接着会调用AQS中的acquire方法。

/**
 * Acquires in exclusive mode, ignoring interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 */
public final void acquire(int arg) {
    //(1)
    if (!tryAcquire(arg) &&
        //(2)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //(3)
        selfInterrupt();
}
复制代码

我们来简要分析一下类的注释:

//以独占方式获取锁,并忽略中断。
//也就是说这个方法对线程的interrupt不做响应,如果线程在阻塞的时候因为interrupt被唤醒,则又会恢复阻塞状态,下文会进行分析它的实现。
/*Acquires in exclusive mode, ignoring interrupts.*/
复制代码
//该方法会至少调用一次tryAcquire方法,如果返回成功,则方法结束不继续执行。否则将线程放入队列(排队)。
//tryAcquire方法会尝试获取锁,如果获取成功,则当前线程继续执行。否则线程就进行排队。
/* Implemented by invoking at least once {@link #tryAcquire},returning on success.Otherwise the thread is queued*/
复制代码
//可能反复阻塞和解除阻塞(阻塞 - interrupt - 阻塞),直到成功(获取到锁)
/* possibly repeatedly blocking and unblocking, invoking {@link #tryAcquire} until success*/
复制代码

代码(1)调用tryAcquire(arg)方法获取锁,如果成功获取到锁,由于进行了非运算,则如果成功获取到锁,则直接返回,不进行后续操作。

如果获取锁失败,非运算后返回true,则会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,按照注释所说,这里是将当前线程进行了排队。

最后如果两个条件都返回true,则会将自己打断selfInterrupt(),在正常的加锁过程中,其实不会出现两个都返回true的情况,这里是为reentrantLock.lockInterruptibly做的代码复用。

小结:对reentrantLock会根据构造器生成的锁类型来调用不同的lock方法。如果成功获取锁,则方法返回,线程继续执行,如果获取锁失败,则线程进行排队。(但是线程如果获取不到锁,并不是直接排队这么简单)

tryAcquire方法分析。

现在我们先来分析一下(1)也就是tryAcauire方法,在AQS中该方法是个抽象方法,需要具体的实现,这里的是公平锁的tryAcquire方法。

/**
 * Fair version of tryAcquire.  Don't grant access unless
 * recursive call or no waiters or is first.
 */
protected final boolean tryAcquire(int acquires) {
  //(1.1)
    final Thread current = Thread.currentThread();
  //(1.2)
    int c = getState();
  //(1.3)
    if (c == 0) {
        //(1.4)
        if (!hasQueuedPredecessors() &&
          //(1.5)
            compareAndSetState(0, acquires)) {
          //(1.6)
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //(1.7)
    else if (current == getExclusiveOwnerThread()) {
        //(1.8)
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        //(1.9)
        setState(nextc);
        return true;
    }
    return false;
}
复制代码

现在我们逐行进行分析 (1.1)获取当前线程 (1.2)getState()是AQS中的方法,返回的是获取锁的线程重入的次数(下文分析)。由于state是volatile声明的,所以保证了可见性。

/**
 * Returns the current value of synchronization state.
 * This operation has memory semantics of a {@code volatile} read.
 * @return current state value
 */
protected final int getState() {
    return state;
}
复制代码
/**
 * The synchronization state.
 */
private volatile int state;
复制代码

(1.3)判断线程状态是否为0,我们假设现在只有一个线程T1调用lock方法,由于这里的state是int类型的,默认值为0,所以c == 0返回true,进入第一个if代码块。 (1.4)hasQueuedPredecessors(),判断队列是否初始化,如果初始化,非运算后直接返回。如果队列没有初始化,则继续执行。

/**
 * Queries whether any threads have been waiting to acquire longer
 * than the current thread.
 *
 * <p>An invocation of this method is equivalent to (but may be
 * more efficient than):
 *  <pre> {@code
 * getFirstQueuedThread() != Thread.currentThread() &&
 * hasQueuedThreads()}</pre>
 *
 * <p>Note that because cancellations due to interrupts and
 * timeouts may occur at any time, a {@code true} return does not
 * guarantee that some other thread will acquire before the current
 * thread.  Likewise, it is possible for another thread to win a
 * race to enqueue after this method has returned {@code false},
 * due to the queue being empty.
 *
 * <p>This method is designed to be used by a fair synchronizer to
 * avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
 * Such a synchronizer's {@link #tryAcquire} method should return
 * {@code false}, and its {@link #tryAcquireShared} method should
 * return a negative value, if this method returns {@code true}
 * (unless this is a reentrant acquire).  For example, the {@code
 * tryAcquire} method for a fair, reentrant, exclusive mode
 * synchronizer might look like this:
 *
 *  <pre> {@code
 * protected boolean tryAcquire(int arg) {
 *   if (isHeldExclusively()) {
 *     // A reentrant acquire; increment hold count
 *     return true;
 *   } else if (hasQueuedPredecessors()) {
 *     return false;
 *   } else {
 *     // try to acquire normally
 *   }
 * }}</pre>
 *
 * @return {@code true} if there is a queued thread preceding the
 *         current thread, and {@code false} if the current thread
 *         is at the head of the queue or the queue is empty
 * @since 1.7
 */
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());
}
复制代码

简要分析一下注释

//查询是否有线程比当前的线程等待了更多的时间。
//我们这里讨论的是公平锁,所以遵循先进先出的原则。先来的线程会先获取锁,这很好理解
/*Queries whether any threads have been waiting to acquire longer than the current thread. */
复制代码
//调用该方法相当于执行getFirstQueuedThread() != Thread.currentThread() && hasQueuedThreads()
//也就是说这个方法就是判断队列的第一个线程是否是当前线程而且当前装线程的队列是否被初始化,但是这么写的代码执行效率比较高    
/* * <p>An invocation of this method is equivalent to (but may be
     * more efficient than):
     *  <pre> {@code
     * getFirstQueuedThread() != Thread.currentThread() &&
     * hasQueuedThreads()}</pre>
*/
复制代码
//由于中断和超时在任何时候发生所以不能保证其他的线程先于当前线程获得锁,当队列为空时,也不能保证当前线程先于其他线程返回线程为空的这个结果。
//很好理解,CPU是按照时间片执行的,这段代码有没有同步机制,所以可能会出现两个线程都返回该队列为空的情况。但是后续代码中的CAS操作可以保证只有一个拿到锁,并初始化队列。
/* * <p>Note that because cancellations due to interrupts and
 * timeouts may occur at any time, a {@code true} return does not
 * guarantee that some other thread will acquire before the current
 * thread.  Likewise, it is possible for another thread to win a
 * race to enqueue after this method has returned {@code false},
 * due to the queue being empty.
*/
复制代码

简要的说这个方法就是用来判断队列是否为空,如果队列不为空,当前的线程是不是队列头部的第一个线程。 现在我们来分析代码

        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
复制代码

这是一个简单的赋值操作,来获取队列的头尾节点,这个队列是个双向的队列。Node是队列的节点,每个节点有一个前驱和后继,以及存放着排队的线程和排队线程的状态。

static final class Node{
    volatile Node prev;//前驱
    volatile Node next;//后继
    volatile Thread thread;//线程
    volatile int waitStatus;//线程的状态
}
复制代码

在AQS中,head和tail,并未被初始化,所以都为null

    private transient volatile Node head;
    private transient volatile Node tail;
复制代码

现在我们假设只有一个线程T1进入了该方法,那么对于第一个判断tail != head 不成立,该方法就直接返回false。说明队列未初始化。 现在我们返回到tryAcquire的(1.4),这时由于我们只有一个线程,则(1.4)返回了false,取反后条件成立,于是进入第二个判断条件。

(1.5)接着执行CAS操作,将acquires的值赋值给state,由之前的代码可以看出这个值为1,如果赋值成功,则说明成功获取到锁。之前说过,由于hasQueuedPredecessors()判断队列是否为空的方法不是同步的,所以当执行完代码(1.4)后可能有多个线程都认为线程是空的,所以都会来改变state的状态,这时作者在(1.5)中使用了CAS操作,保证了有且只有一个线程能成功改变state的状态。

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
复制代码

这里调用了Unsafe类中的CAS操作。CAS操作的原理可自行去了解。 (1.6)将当前线程赋值给exclusiveOwnerThread 这个变量,这个变量用来标记当前获得锁的线程。

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}
复制代码

最后方法返回true。 小结:公平锁中的tryAcquire方法,会先获取到当前线程的状态,如果当前状态的state为0(0为初始状态),就会判断当前队列是否被初始化,或者当前队列的第一个线程是否是当前线程。如果是,则尝试改变state的状态,标记为1,也就是获取到锁被重入了一次,并记录当前线程,返回true。否则直接返回false,并不尝试改变state的状态。

图片.png
现在先让我们返回AQS中的acquire方法。如果tryAcquire返回的结果为true。那么在取反后(1)为false线程直接返回,如果未获取到锁,那么进入(2),将线程加入队列中。

public final void acquire(int arg) {
    //(1)
    if (!tryAcquire(arg) &&
        //(2)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        //(3)
        selfInterrupt();
}
复制代码

在流程图中可以看到我们只分析了state == 0的情况,现在我们再来分析 state != 0 的情况。

    //(1.7)
    else if (current == getExclusiveOwnerThread()) {
        //(1.8)
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        //(1.9)
        setState(nextc);
        return true;
    }
复制代码

(1.7)如果当前的线程state不等与0,那么它就会判断尝试获取锁的线程是不是当前线程,getExclusiveOwnerThread()方法用来获取当前已经获取到锁的线程,在(1.6)中我们已经保存了当前获取到锁的线程。

protected final Thread getExclusiveOwnerThread() {
    return exclusiveOwnerThread;
}
复制代码

如果不是,则tryAcquire(arg)直接返回false。如果是则更新state的状态。那么如何更新呢?由上文的传参可知参数acquires的值为1,c指代当前state的值,所以nextc可以理解为state++; 正常情况下不会出现nextc小于0的情况,这里不做讨论。最后保存state的值,并返回true,标志获取锁成功。

这也就说明了,reentrantLock是个可重入的锁,可重入次数理论上是int的最大值。

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock(true);
    for(int i = 0; i <= Integer.MAX_VALUE;i++){
        reentrantLock.lock();
    }
}
//这段代码会抛出 Maximum lock count exceeded 异常,也说明了重入次数是有限制的
复制代码

到这里tryAcquire就分析完成了,我们能得出一个结论,如果线程是交替执行的,那么reentrantLock.lock不会初始化队列(因为tail和head都为null),也不会因为加锁而影响代码的性能(并没有调用操作系统的方法)。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法分析

假如获取锁失败了,就会调用这个方法获取队列进行排队,现在我们先来分析一下addWaiter。

/**
 * Creates and enqueues node for current thread and given mode.
 *
 * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
 * @return the new node
 */
private Node addWaiter(Node mode) {
    //(2.1)
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //(2.2)
    Node pred = tail;
    //(2.3)
    if (pred != null) {
        //(2.4)
        node.prev = pred;
        //(2.5)
        if (compareAndSetTail(pred, node)) {
            //(2.6)
            pred.next = node;
            return node;
        }
    }
    //(2.7)
    enq(node);
    return node;
}
复制代码

这段代码乍看起来挺复杂,其实是双向链表的新增插入操作。 在代码(2.1),我们新增了一个Node节点,并将当前线程和mode传入。在Node类中定义了一些静态变量表标识这些mode,

    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;
复制代码

这里传入的是Node.EXCLUSIVE,也就是null.读者不要将刚才的state与这个mode混淆,state在AQS中表示当前线程的重入次数,初始为0。

现在假设由两个线程先后获取锁,当T1线程来的时候,经过tryAcquire发现队列为空,于是CAS成功获取锁返回执行,但是执行了很长时间也没有释放锁,这时T2线程也来获取锁,由于state == 0 不成立,直接返回false。这时会先调用addWaiter来创建Node。

在初始的时候tail 和head都是空的,我们先创建了一个新节点T2,(2.2)将尾部赋值给节点prev,由于tail未初始化为空,所以prev也是空的,于是执行(2.7)代码。

这个方法会将新的节点插入队列,如果队列没有初始化,就初始化队列。其中传入的参数node是T2节点。

/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
private Node enq(final Node node) {
    for (;;) {
        //(2.7.1)
        Node t = tail;
        //(2.7.2)
        if (t == null) { // Must initialize
            //(2.7.3)
            if (compareAndSetHead(new Node()))
                //(2.7.4)
                tail = head;
        } else {
            //(2.7.5)
            node.prev = t;
            //(2.7.6)
            if (compareAndSetTail(t, node)) {
                //(2.7.7)
                t.next = node;
                return t;
            }
        }
    }
}
复制代码

在第一次for循环 tail是空的,所以t = null。 (2.7.1)将tail 赋值给临时节点t,刚才说过,tail是空的,满足if条件,于是初始化队列。注意在(2.7.3)中同样是个CAS操作,new 了一个空的新节点给head(不是我们用新线程创建的T2),然后将尾部指向头部。 在第二次for循环 先将尾节点赋值给t (2.7.1)这时节点t就不是null了,是一个空的新节点,于是(2.7.2)条件不满足,跳到(2.7.5)。 将空的节点t当作T2节点的前驱,(2.7.6)然后尝试将T2作为尾部。(2.7.7)最后空节点的后继指向T2。**简单的说就是将刚创建的节点,插入到队列的尾部。**最后返回t,也就是空节点被返回了。

在这过程中如果CAS赋值失败了,由于外层有个死循环,所以一定会保证当前线程的节点成功的加入到队列中。 这里为什么要先创建一个空的node呢,我们暂时可以简单看作为是指代获取到锁的那个线程,也就是T1。 我们再返回代码(2.7)发现刚刚这个t的返回值并没有被使用,返回的是我们刚创建的node,也就是队列的尾节点。

图片.png
图片.png
小节:addWaiter方法会将新创建的node连接在队列的尾部,如果队列未初始化,则先将队列初始化,需要特别注意的是,队列的头节点是一个空的node。

那如果是(2.3)pred != null的情况呢?我们再次假设现在又来了一个线程T3,T1获得锁正在运行,T2已经初始化完成队列,在队尾排队.这个队列size =2,头节点是空的node。新节点node为T3 (2.2)Node pred = tail 也就是pred的值为T2,pred != null进入(2.4)将T3的前驱指向T2在尝试将T3设置为尾节点。也就是将T3加入尾部,如果还有T4,T5...也是一样处理。

现在我们开始分析acquireQueued(final Node node, int arg) 方法

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //(2.8)                
            final Node p = node.predecessor();
            //(2.9)                
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //(2.10)                
            if (shouldParkAfterFailedAcquire(p, node) &&
                //(2.11)      
                parkAndCheckInterrupt())
                //(2.12)
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

这里也是一个死循环,代码(2.8)调用了predecessor()。这个方法就是返回当前节点的前驱。 同样我们假设还是只有两个线程T1,T2,node指代的是T2线程。这里返回了T2的前驱.所以p=空的node(上文分析过,队列的头节点是个空的Node)。

final Node predecessor() throws NullPointerException {
    Node p = prev;
    if (p == null)
        throw new NullPointerException();
    else
        return p;
}
复制代码

(2.9)p是不是头节点,很显然是的,接着又执行了一次tryAcquire。啥意思呢,就是T2在这里自旋了一次,尝试获取锁,因为可能T1在T2创建队列的时候就已经释放锁了。那为什么p == head成功后才能执行tryAcquire呢,因为只有排在第一位的线程才有资格尝试获取锁,排在后面的线程就没有必要执行这个操作了(空节点指代获得锁的线程,并不是排队线程,第一个排队的是T2)。

如果成功获取到锁,那么返回false,T2线程获得锁继续执行,如果自旋失败,执行(2.10)。这个方法用来更新node的状态,并判断当前线程是否应该进行阻塞挂起了。

/**
 * Checks and updates status for a node that failed to acquire.
 * Returns true if thread should block. This is the main signal
 * control in all acquire loops.  Requires that pred == node.prev.
 *
 * @param pred node's predecessor holding status
 * @param node the node
 * @return {@code true} if thread should block
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //(2.10.1)
    int ws = pred.waitStatus;
    //(2.10.2)        
    if (ws == Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    //(2.10.3)        
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        //(2.10.4)        
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        //(2.10.5)        
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
复制代码

(2.10.1)ws用来获取当前线程的等待状态,我们之前在创建Node时,并没有赋初始值,所以ws =0。顺便说一下Node.SIGNAL = -1,表示如果节点处于这个状态,那么这个节点的后继节点将会或者已经处于阻塞状态(排队挂起)。也就是说如果T1的ws = -1,那么T2将会被阻塞,或者已经被阻塞。

(2.10.5)明显ws = 0不符合条件进入else,将pred这个node状态这是为-1,pred节点是代表T1的空节点,这也表示T2线程将要进入阻塞挂起状态。最后返回false。这里的CAS为什么不进行自旋呢,因为当前线程CAS如果失败了,会返回到acquireQueued方法的循环,又一次执行(2.10)方法改变状态。

回到acquireQueue方法,(2.10)返回false于是第一次循环结束。

第二次循环p并没有改变值,还是空节点,(2.9)p==head返回true,又进行了一次自旋!,如果还是获取不到锁,进入(2.10.1),这时ws已经改变过一次了,值尾-1,直接返回true。 **所以第一个排队的线程进行了两次自旋。**为什么不是只自旋一次,或者自旋3次或者更多呢,一个因为将线程阻塞的开销较大,如果线程能够不阻塞排队,那么最好不要,所以需要多给一次机会,而且Node的状态改变也需要两次的循环。有其他看法的也欢迎讨论。

(2.11.1)这里将线程阻塞了.于是整个加锁过程结束,直到线程再次被唤醒,或者打断(interrupt)。 如果线程被打断,那么执行(2.11.2)重置打断状态,并返回true,于是(2.12)将interrupt赋值为true,之后继续循环,并不做响应,再一次阻塞线程。所以reentrantLock.lock不对中断做出响应。

    private final boolean parkAndCheckInterrupt() {
        //(2.11.1) 
        LockSupport.park(this);
        //(2.11.2)        
        return Thread.interrupted();
    }
复制代码

公平锁总结:

  1. 在公平锁中,如果线程获取锁时,会先判断ReentrantLock中state的状态(也是锁被重入的次数),如果锁未被持有,接着检查队列是否初始化,如果未初始化,尝试就获取锁。如果锁已经被初始化,或者队列已经被初始化,则让当前线程排队。可以参考上文的流程图。
  2. 在线程排队之前,会先初始化队列,队列的第一个节点是空的,指代当前已经获取到锁的线程(不是null)。
  3. 在线程加入队列后,如果这个线程是第一个排队的线程(不是空节点的线程,可以理解为上文的T2),会进行两次自旋尝试获取锁。
  4. 如果线程在阻塞(park)时被打断(interrupt),并不会抛出异常,或做出其他响应,而是重置打断的状态,继续阻塞。

非公平锁加锁过程

当我们使用如下方式创建的时候,reenTrantLock为非公平锁

    ReentrantLock reentrantLock = new ReentrantLock();
    ReentrantLock reentrantLock1 = new ReentrantLock(false);
复制代码

现在我们调用reenTrantLock.lock()方法。现在该方法会调用非公平锁的lock。

    public void lock() {
        sync.lock();
    }
复制代码

我们可以先回顾一下公平锁的加锁方法,它在加锁之间回显判断state的状态是否为0,也就是当前锁是否是可用的。如果为0,还要判断队列的状态是否为空,之后才有资格尝试获取锁。而在非公平锁的lock中,每个线程在加锁的时候不管三七二十一都会先去尝试获取锁,这就会出现正在排队的线程锁,被刚来的线程抢占的情况。

/**
 * Performs lock.  Try immediate barge, backing up to normal
 * acquire on failure.
 */
final void lock() {
    //(1)
    if (compareAndSetState(0, 1))
        //(2)
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //(3)
        acquire(1);
}
复制代码

(1)代码CAS抢占锁,如果抢占成功则记录当前线程。如果执行失败执行(3),这里同样是AQS中的acquire方法。

public final void acquire(int arg) {
    //(3.1)
    if (!tryAcquire(arg) &&
        //(3.2)
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
复制代码

接着就调用了非公平锁中的tryAcquire方法。

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
复制代码

接着调用了sync中的nonfairTryAcquire(int acquires)方法。在公平锁中就直接将方法实现了,非公平锁却写在了父类中。

    /**
     * Performs non-fair tryLock.  tryAcquire is implemented in
     * subclasses, but both need nonfair try for trylock method.
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //(3.1.1)
            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;
    }
复制代码

非公平锁语公平锁的tryAcaquire几乎相同,只不过在执行(3.1.1)之前要先判断队列是否已经初始化,或者对头线程是不是当前线程。其他代码都一样,这里就不在重复读者可自行理解。

最后的入队操作acquireQueued(addWaiter(Node.EXCLUSIVE), arg))和公平锁是一样的。不再过多赘述。

非公平锁总结

  1. 非公平锁在加锁之前会先抢占锁。
  2. 非公平锁在抢占锁失败后,与公平锁一样也会执行tryAcquire方法,但是非公平锁的tryAcquire在发现当前锁可用之后,不会继续判断队列是否初始化,或者对头是否是当前线程,而是直接尝试获取锁,如果失败就加入队列。



原文地址:访问原文地址
快照地址: 访问文章快照