java基础-线程状态分析-ReentrantLock源码分析1

309 阅读3分钟

在前文已经讲述ReentrantLock如何使用,本章将对源码进行分析。

image.png

在ReentrantLock的构造函数中,我们看到,默认采用的是非公平锁

public ReentrantLock() {
    sync = new NonfairSync();
}

image.png 在类图中,我们看到了如雷贯耳的AQS,下面先分析AQS

1.1. AQS是啥

根据源码doc,AQS是一个依赖FIFO,用于实现堵塞锁和同步器的框架,且其子类最好定义为内部类

其核心就是双端同步队列

1.2 双端核心队列

队列有Node节点构成,其是一个内部静态类,

*      +------+  prev +-----+       +-----+
* head |      | <---- |     | <---- |     |  tail
*      +------+       +-----+       +-----+

源码如下:

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    //共享锁等待的节点
    static final Node SHARED = new Node();
    /** 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;

    /**
     * Status field, taking on only the values:
     *   SIGNAL:     The successor of this node is (or will soon be)
     *               blocked (via park), so the current node must
     *               unpark its successor when it releases or
     *               cancels. To avoid races, acquire methods must
     *               first indicate they need a signal,
     *               then retry the atomic acquire, and then,
     *               on failure, block.
     *   CANCELLED:  This node is cancelled due to timeout or interrupt.
     *               Nodes never leave this state. In particular,
     *               a thread with cancelled node never again blocks.
     *   CONDITION:  This node is currently on a condition queue.
     *               It will not be used as a sync queue node
     *               until transferred, at which time the status
     *               will be set to 0. (Use of this value here has
     *               nothing to do with the other uses of the
     *               field, but simplifies mechanics.)
     *   PROPAGATE:  A releaseShared should be propagated to other
     *               nodes. This is set (for head node only) in
     *               doReleaseShared to ensure propagation
     *               continues, even if other operations have
     *               since intervened.
     *   0:          None of the above
     *
     * The values are arranged numerically to simplify use.
     * Non-negative values mean that a node doesn't need to
     * signal. So, most code doesn't need to check for particular
     * values, just for sign.
     *
     * The field is initialized to 0 for normal sync nodes, and
     * CONDITION for condition nodes.  It is modified using CAS
     * (or when possible, unconditional volatile writes).
     */
    volatile int waitStatus;

    /**
     * Link to predecessor node that current node/thread relies on
     * for checking waitStatus. Assigned during enqueuing, and nulled
     * out (for sake of GC) only upon dequeuing.  Also, upon
     * cancellation of a predecessor, we short-circuit while
     * finding a non-cancelled one, which will always exist
     * because the head node is never cancelled: A node becomes
     * head only as a result of successful acquire. A
     * cancelled thread never succeeds in acquiring, and a thread only
     * cancels itself, not any other node.
     */
     //前驱节点
    volatile Node prev;

    /**
     * Link to the successor node that the current node/thread
     * unparks upon release. Assigned during enqueuing, adjusted
     * when bypassing cancelled predecessors, and nulled out (for
     * sake of GC) when dequeued.  The enq operation does not
     * assign next field of a predecessor until after attachment,
     * so seeing a null next field does not necessarily mean that
     * node is at end of queue. However, if a next field appears
     * to be null, we can scan prev's from the tail to
     * double-check.  The next field of cancelled nodes is set to
     * point to the node itself instead of null, to make life
     * easier for isOnSyncQueue.
     */
     //后继节点
    volatile Node next;

    /**
     * The thread that enqueued this node.  Initialized on
     * construction and nulled out after use.
     */
    volatile Thread thread;

    /**
     * Link to next node waiting on condition, or the special
     * value SHARED.  Because condition queues are accessed only
     * when holding in exclusive mode, we just need a simple
     * linked queue to hold nodes while they are waiting on
     * conditions. They are then transferred to the queue to
     * re-acquire. And because conditions can only be exclusive,
     * we save a field by using special value to indicate shared
     * mode.
     */
     //共享或者条件变量的node
    Node nextWaiter;

    /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * Returns previous node, or throws NullPointerException if null.
     * Use when predecessor cannot be null.  The null check could
     * be elided, but is present to help the VM.
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

Node节点的状态有如下几种:

  1. 默认状态为 0;
  2. waitStatus > 0 (CANCELLED 1) 说明该节点超时或者中断了,需要从队列中移除;
  3. waitStatus = -1 SIGNAL 当前线程的前一个节点的状态为 SIGNAL,则当前线程需要阻塞(unpark);
  4. waitStatus = -2 CONDITION -2 :该节点目前在条件队列,如果不使用condition用不到;
  5. waitStatus = -3 PROPAGATE -3 :releaseShared 应该被传播到其他节点,在共享锁模式下使用。

AQS有两个队列一个是同步队列,一个是条件队列,其节点共用Node,发现一张比较好的图,分别如下

同步队列

image.png

条件队列

image.png

1.3 lock源码分析

以公平锁为例,lock整体流如下:

image.png

acquire()方法

final void lock() {
    if (compareAndSetState(0, 1))
1.         setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
public final void acquire(int arg) {
//方法后面会介绍
2.     if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire分析如下:

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()) {
    //此处就是可重入,将状态值增加acquires,一般是加1
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //返回false,说明要去队列排队
    return false;
}

addWaiter分析如下: 将当前线程与node绑定,并加入到同步队列

private Node addWaiter(Node mode) {
//线程和node绑定
    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;
        //采用反射,将node节点设置为尾节点
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

enq()方法如下:

3. private Node enq(final Node node) {
//自旋
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
        //使用CAS,初始化头尾节点
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            //第一个是期望值,第二值是更新后的值,方法利用反射设置tail的值
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

假定已经初始化head节点,那么head和tail指向是同一个对象,红色节点是已队列,蓝色是新增的,上面的流程步骤如下,其中header是虚拟节点:

image.png

acquireQueued分析如下:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //进行自旋
        for (;;) {
        //这不分方法很重要,和后面的释放有着密切关系
       //在自选过程中,会一直前检查前面一个,若其是前面一个节点,那么在没有
       //外来线程继续抢占锁锁的情况,下一个获得到锁的线程就这这个锁
            final Node p = node.predecessor();
            //因为head是虚拟节点,所以此处线程可以直接去获取锁
4.             if (p == head && tryAcquire(arg)) {
                setHead(node);
                //在释放锁的过程中,我们没有发现将head节点移除的过程,其实这里
                //将next=null,就是将head节点给回收掉
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //parkAndCheckInterrupt方会执行LockSupport.park方法,将当前线程挂起,等待unprk唤醒
            //当然,只有shouldParkAfterFailedAcquire执行成功才会执行后面的方法
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
               
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire分析如下:

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.
         */
         //将前置节点的状态置为SIGNAL,注意这样的话head节点也是SIGNAL状态,在释放锁的时候
         //会用到
5.         compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

根据上面可知,只有前驱节点的状态为SIGNAL,才能将当前线程机型park(线程未waiting状态)

parkAndCheckInterrupt分析如下:

private final boolean parkAndCheckInterrupt() {
//使线程置为waiting状态,并挂起

6.     LockSupport.park(this);
    return Thread.interrupted();
}

上面打断挂起线程,有两种方式,1:LockSupport.unpark(thread); 2:thread.interrupt() 如果是LockSupport.unpark打断,那么Thread.interrupted()为false; 如果是thread.interrupt()打断,Thread.interrupted()返回为true,同时将中断标志位 置为false,这个和1.2.1 acquire()对应,因为此处将中断位置为false了,那么1.2.1就要将自中断,将中断标志位置为true

#### 1.3 unlock源码分析
public final boolean release(int arg) {
 if (tryRelease(arg)) {
 //当前节点释放后,要把head的下一个节点唤醒
     Node h = head;
     if (h != null && h.waitStatus != 0)
         unparkSuccessor(h);
     return true;
 }
 return false;
}

tryRelease方法

protected final boolean tryRelease(int releases) {
//没有使用cas,因为释放的时候,肯定是持有lock的
    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;
}
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.
     */
     //从后向前找到下一个状态不是canceled的节点
    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);
}

1.4 unlock源码分析

public final boolean release(int arg) {
//将exclusiveOwnerThread设置为null
    if (tryRelease(arg)) {
    //经过上一步锁已经释放了,下面只是将同步队列的head节点后面需要唤醒的节点唤醒
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor方法:

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)
    //会唤醒park的thread
        LockSupport.unpark(s.thread);
}

释放锁的实际分为两步,第一步释放锁,第二步将节点置为初始状态并park线程,锁释放后,可能是同步队列中的线程获取到,也有可能是新来的线程获取到

总结

单独看加锁和释放所得代码,比较难串到一起,代码确实很巧妙写的,利用cas和Locksupport.park和unpark实现了锁

不考虑重入,以公平锁为例,多个线程在不停抢锁 加锁流程:

image.png 上面个点的标注在代码中有体现,要特别注意的是header存在且header状态为signal说明有线程已经获取到锁了,其他线程只能等待

exclusiveOwnerThread =thread 释放锁流程:

image.png