ReentrantLock个人笔记

168 阅读4分钟

前言

park 和 unpark:

LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport提供的两个主要方法就是park和unpark。park我们可以理解为挂起,而unpark我们理解为唤醒。 LockSupport同步线程和wait/notify不一样,LockSupport并不需要获取对象的监视器,而是给线程一个“许可”(permit)。而permit只能是0个或者1个。unpark会给线程一个permit,而且最多是1;而park会消耗一个permit并返回,如果线程没有permit则会阻塞。LockSupport.unpark(“线程名”);

AQS双向链表的节点node:

    volatile int waitStatus;

    volatile Node prev;

    volatile Node next;

    volatile Thread thread;

    Node nextWaiter;

lock 过程

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

公平锁和非公平锁在这里的差别就是,公平锁在加锁之前会去判断:hasQueuedPredecessors 是否有前node,也就是需不需要排队。非公平锁不会进行判断,直接通过CAS进行尝试加锁。

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;
}
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;
}
  1. 首先是没有线程获取锁的情况。假如这时来了一个线程A加锁,发现 state == 0 ,然后会判断自己是否需要排队,这里不需要排队。这时候线程A会通过CAS的方式,去修改锁的状态。然后设置exclusiveOwnerThread(当前持有锁的线程) 为线程A。
protected final boolean compareAndSetState(int expect, int update) {
    return STATE.compareAndSet(this, expect, update);
}
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}
  1. 当线程A持有锁后,这时候又来了一个线程B尝试加锁。线程B发现锁的状态并不是0,并且当前持有锁的线程也不是自己,那么线程B则会进行入队操作。也就是执行下面的 addWaiter 和 acquireQueued 方法。
public final void acquire(int arg) {
   if (!tryAcquire(arg) &&
       acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
       selfInterrupt();
}

addWaiter : 这里是一个自旋的操作,因为线程A加锁的时候并不需要入队,所以 tail 现在为 null 。那么首先就会进入 else 中去 initializeSyncQueue 。这里第一次进队列,会new 一个 线程为null 的node,并且把head 和 tail 都设置成这个 node。然后再把线程B 生成的node,维护成 null线程 node的下一个节点,并且把 线程B node 设置成 tail。
ps : 双向链表的第一个node,永远是线程为null的,获取当前锁的线程不会在双向链表中。

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();
        }
    }
}
/**
 * Initializes head and tail fields on first contention.
 */
private final void initializeSyncQueue() {
    Node h;
    if (HEAD.compareAndSet(this, null, (h = new Node())))
        tail = h;
}

acquireQueued : 这个方法主要的作用就是去阻塞获取不了锁的线程B。这里又是自旋操作,首先会去判断一下线程B节点的上一个节点是不是head,如果是head那么会再尝试去加锁 tryAcquire(因为有可能执行到这里线程A已经释放锁了,但是还没唤醒下一个。只有当前节点的上一个节点是head才会去尝试 tryAcquire)。然后就是比较难的shouldParkAfterFailedAcquire方法了。

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

shouldParkAfterFailedAcquire :设置前一节点的waitStatus为 SIGNAL(-1),并且再次回到acquireQueued再进行一次tryAcquire,然后回到shouldParkAfterFailedAcquire,这时满足if (ws == Node.SIGNAL),然后返回 true 。然后执行 parkAndCheckInterrupt。
Node有5中状态,分别是:CANCELLED(1),SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)、默认状态(0)
CANCELLED :在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点, 其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化
SIGNAL :只要前置节点释放锁,就会通知标识为SIGNAL状态的后续节点的线程

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.
         */
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
    }
    return false;
}

parkAndCheckInterrupt :park线程B。线程B执行到这里就暂时挂起了。

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

这里可以先去看下面的 unlock过程

然后被唤醒了执行 return Thread.interrupted(),然后就回到了 acquireQueued 方法中的自旋代码里。这时候就会加锁成功,然后执行 setHead(node);

if (p == head && tryAcquire(arg)) {
    setHead(node);
    p.next = null; // help GC
    return interrupted;
}

setHead :会把当前线程,也就是线程B的这个node中的 thread 设置成null,并且把这个node设置成head,并且把之前的head出队。所以说持有锁的线程不会在双向链表中。

/**
 * Sets head of queue to be node, thus dequeuing. Called only by
 * acquire methods.  Also nulls out unused fields for sake of GC
 * and to suppress unnecessary signals and traversals.
 *
 * @param node the node
 */
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

unlock 过程

public void unlock() {
    sync.release(1);
}

因为之前线程B进入了双向链表,在shouldParkAfterFailedAcquire方法中修改了head节点中的waitStatus,所以会去唤醒。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

释放锁,如果states为0 ,设置当前持有锁的线程为null。

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

唤醒后继节点,也就是可以唤醒之前的park的线程B了。

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;
        // 如果ws < 0表明是正常状态,置为0表示不需要唤醒后继节点这类操作
        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.
         */
        // ws大于0表示已经取消了,那就找到第一个未取消的节点来唤醒,不过这里是通过从后往前扫的方式处理的
        // 通常情况下是唤醒node.next节点,但是考虑到节点被取消或者是null的情况,需要从后面往前扫
        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);
    }