ReentrantLock中Fair公平锁的执行过程源码分析

69 阅读5分钟

1.原理交代
在这里插入图片描述

  • state:表示重入锁的重入次数,如果为0是表示锁无人获得。
  • head:直线头结点,默认情况下他的后继结点无值。此时头结点和尾结点均指向此处。
  • tail:指向尾结点,及最后一个结点,初始情况下和head指向同一个首结点。
  • exclusiveOwnerThread:存放当前后的锁的线程。
  • 当state=0 && exclusiveOwnerThread = null时可以抢占次锁。 当锁闲置是有新线程过来抢锁时,无须进入队列,可以和第一个结点一并抢占锁。
    2.测试代码,注意真正执行的时候是高并发进来,咱们抽丝剥茧,进入时间内部看看它到底怎么执行的。
package com.yuhl.concurrent.AQS;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author yuhl
 * @Date 2020/10/18 17:26
 * @Classname Fair001
 * @Description AQS非公平锁的源码解读
 */
public class Fair001 {

    public static void main(String[] args) {

        //构建公平锁。如果无参默认为非公平锁哦!
        ReentrantLock lock = new ReentrantLock(true);
        try {
            lock.lock();//加锁
            TimeUnit.SECONDS.sleep(1);//次任务执行1
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

3.当第一个并发线程T1进来时的执行过程:
当代码执行到

lock.lock();//加锁

这句的时候发生了一系列的故事。我们进入其中一探究竟吧:

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

调用:FairSync.sync.lock():,特别注意,现在讨论的是公平锁:
在这里插入图片描述

final void lock() {
            acquire(1);
        }

lock,调用acquire(1);方法

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

acquire(int arg)方法传入的参数为1,这个1就是要更新的state中的。
此处有三个方法,我们先看第1个tryAcquire(arg)方法(试着去获取锁),我们是第一个线程T1进来的,所有他肯定拿得到锁的哦,我们详细看下他的源码吧:
在这里插入图片描述

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();//第一次进来,state默认为0,没线程占用。
            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;
        }

第一个线程进来,state默认为0,没线程占用,所以走上面的if方法,其中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());
    }

此时头节点h,尾节点s均为null,及初始化了头尾节点。由于此时队列为空故返回false。

  • 此时开始执行第二个方法compareAndSetState(0, acquires):
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

调用底层的compareAndSwapInt方法:

 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

设置state=1。然后进入if代码块中执行setExclusiveOwnerThread(current)方法,这只exclusiveOwnerThread = currentThread。至此占坑成功。

4.此时T1占坑中因为T1需要占坑1秒钟,这可是很长的时间啊,此时在0.2秒T2进来了:一下前几部和前面T1进来时一模一样哦。
执行lock方法当代码执行到

lock.lock();//加锁

这句的时候发生了一系列的故事。我们进入其中一探究竟吧:

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

调用:F在这里插入代码片airSync.sync.lock():,特别注意,现在讨论的是公平锁lock():

        final void lock() {
            acquire(1);
        }

然后 acquire(1);方法:

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

到此和T1的调用是一致的,现在开始

  • 调用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;
        }
    }

此时的state为1,已经被占坑了。current != getExclusiveOwnerThread(),因为current=T2,而exclusiveOwnerThread=T1所以else if的条件也不满足所以,代码往下走返回false。

  • acquire()方法中的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)中的addWaiter(Node.EXCLUSIVE)方法:
    插入说明:Node里面两个对象,下一个Node,和当前的Thread
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

回归正题开始调用addWaiter(Node.EXCLUSIVE)方法,此时尾节点是null(因为AQS中只有头和尾):

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;//T2的前置指向head
            if (compareAndSetTail(pred, node)) {
                pred.next = node;//head的后置指向T2
                return node;
            }
        }
        enq(node);
        return node;
    }

即对象pred 是null,不走if,所以此时直接调用enq(node);方法:

 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;//让未节点也指向这个属性为null的new Node()对象
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
  • for的第一次死循环:让未节和头节点均指向这个属性为null的new Node()对象。
    在这个死循环里面,先调用了compareAndSetHead(new Node())方法,给head节点设置值,之间头和尾均为null,看compareAndSetHead(new Node())代码如下:
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

调用链native到底了了:

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

截图说明如下:
在这里插入图片描述

tail = head;//让未节点也指向这个属性为null的new Node()对象
  • for的第二次死循环:
    走的是sles这个逻辑:在这里插入图片描述
    此时这个头节点和T2对应的节点构建好了。
    AQS的情况如下:
    在这里插入图片描述
    返回了头节点(这个分会没哟意义,因为调用它的方法没有接收这个返回值),跳出了for循环。
    addWaiter()方法到此接收,做了一件把新进入的T2放入了链表中。
  • 开始调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) = acquireQueued(T2, arg)这个方法,主要作用是park住当前的T2线程源码如下:
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)) {//他的前置就是p,而且他重新去 tryAcquire(arg)返回依然是false,因为此时是0.2s, T1那个哥们还没有释放锁呢。
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

调用shouldParkAfterFailedAcquire()方法进行T2线程的park操作,源码如下:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

此处插入一个waitStatus 的状态解释:

waitStatus解释
1:CANCELLED在同步队列中等待的线程等待超时或者被中断,取消继续等待
0初始化状态
-1 :SIGNAL当前结点表示的线程在释放锁后需要唤醒后续节点的线程
-2:CONDITION等待条件状态,在等待队列中
-3:PROPAGATE状态需要向后传播

插入操作结束,回到正题上。
ws为头节点的waitStatus = 0 默认值,所以走compareAndSetWaitStatus(pred, ws, Node.SIGNAL);方法,源码如下:

    /**
     * CAS waitStatus field of a node.
     */
    private static final boolean compareAndSetWaitStatus(Node node,
                                                         int expect,
                                                         int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,
                                        expect, update);
    }

结果如下:设置头结点的waitSattus=-1,意味着,头结点,下次就要唤醒其后节点了。
在这里插入图片描述
此时回到final boolean acquireQueued(final Node node, int arg) 方法开始第二次for循环,第一次for循环,没有park住。看第二次的情况,private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) 中执行下面代码:


```java
if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;

及此返回true。 返回上层方法开始执行parkAndCheckInterrupt()方法,开始去park:

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);//T2线程在这个里park住了,
        return Thread.interrupted();
    }

此处插入一个点1:

//这两个都是对于threadOne来说的
		 //设置中断标志
        threadOne.interrupt();
         //获取中断标志
        System.out.println("isInterrupted:" + threadOne.isInterrupted()); //true

此处插入一个点2:

    //对当前main线程进行终端表示的设置
        Thread.currentThread().interrupt();
        //获取中断标志并重置
        System.out.println("isInterrupted--main:" + threadOne.interrupted());  //true

原因为:interrupted()方法获取的终端的线程的中断标识,不管是threadOne.interrupted()调用的,还是threadTwo.interrupted()调用的,因为,看他的源码,从源码可以看到,调用的是currentThread()当前线程的中断标识,并不取决于那个实例调用的:

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

插入只是结束。
回归正题:
park进入下面方法开会时park主:

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);//这行代码让他park住了。
        setBlocker(t, null);
    }

UNSAFE.park(false, 0L);//这行代码让他park住了。
总结上面的步骤:
1.T1在AQS中抢到了锁,这个锁需要1才能释放,
2.在0.2秒的时候T2来了。此时会发生什么事呢:先初始化头和尾几点为field为null的Node节点;然后构建T2的Node链表;然后和现有的T2Node节点形成一个双向链表;然后去Park住。

5.在0.3秒的时候又来了个线程T3,此时会发生什么事情能?
在这里插入图片描述
整个流程和T2一模一样也挂起了。此时请款如下:
在这里插入图片描述
6.在1秒的时候又来了个线程T4,此时会发生什么事情能?注意此时,T1也结束了。即此时T1调用:

lock.unlock();

需要唤醒Queue中第一个node1即T2,同时,还有一个T4要和他争抢这把锁.
花开两朵,各表一支,

  • 我们先看T2的情况
    public void unlock() {
        sync.release(1);
    }

调用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;
    }

调用 unparkSuccessor(h);方法,h表示的是head节点:

 private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)//头节点当然不为空了。可以从文章上面的图看出来。
            LockSupport.unpark(s.thread);
    }

走最后一行代码s=node.next,说明s是T2:

 if (s != null)//头节点当然不为空了。可以从文章上面的图看出来。
            LockSupport.unpark(s.thread);

解锁成功:

    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

T2解锁。

接下来T2解锁后怎么执行吗?我们回到当时T2挂起的地方,接着执行就可以了这个就是T2挂起的地方:

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);//挂起的代码,
        setBlocker(t, null);
    }

执行

setBlocker(t, null);

然后执行到调用它的地方继续执行:

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

执行return Thread.interrupted();由于在当前线程中认为设置 T2.interrupt();所以Thread.interrupted();返回为false。
插入一个小小的解释:
T2.interrupted(); 如果有认为的 T2.interrupt(),此时T2.interrupted()他会获中断标识,返回为true,如果没有人(程序员)设置,则直接返回的就是false。
插入小知识结束。

继续回调用次方法的地方:

 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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

parkAndCheckInterrupt()返回为false,此时继续下一次for循环,先看第一个if(p == head)中p == head为true,所以会执行关键方法tryAcquire(arg),试着去获取锁,此时花开两朵,各表一支的前一只T2到此接收,他要执行
tryAcquire(arg),

  • 但是另一只T4也通过调用:
lock.lock();//加锁

也同时到达了tryAcquire(arg)这个方法,此时T2和T4就需要真强这把锁了。
在这里插入图片描述
调用:

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

调用:

 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

看到这个compareAndSwap()就放心了。T2和T4他肯定有一个能抢到。

  • 如果T4抢到则T4执行,AQS链表不变。
  • 如果T2签到,则链表发生该变head->T3->T4.(T4需要乖乖的回去排队了)
    到此整个公平锁的AQS机制介绍完毕。