敞开生长之旅!这是我参与「日新方案 12 月更文挑战」的第6天,点击查看活动概况

信任对并发有所了解的同学关于ReentrantLock并不陌生。没错,今天的主角便是可重入锁ReentrantLock

非公正锁

ReentrantLock的无参结构默认就会创立一个非公正锁

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

咱们来看看他是怎么加锁的

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
  • 首要是经过CAS设置state值为1;
  • 哪个线程设置成功了,就将此线程设置成独占锁线程,也便是此线程取得锁;
  • 不然履行acquire(1)。
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

咱们先剖析tryAcquire(arg)acquireQueued(addWaiter(Node.EXCLUSIVE), arg)子办法,最终在回过头来剖析acquire(int arg)办法

先看tryAcquire(arg)办法

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
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;
}
  • 首要获取state的值,判别是否为0,也便是判别是否有线程占用锁;
  • 假如没有线程占用锁,当时线程经过CAS测验设置state值(测验获取锁),假如成功回来true;
  • 假如当时线程和独占锁的线程是同一个线程,则将state添加acquires(可重入),并回来true;
  • 不然回来false。

总结下tryAcquire(arg)办法的效果:测验获取锁,获取到了锁回来true,不然回来false。

addWaiter(Node.EXCLUSIVE)办法首要是做了一个线程入队的操作,这儿就不细讲;

首要来看看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个办法。

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

能够看到这个办法的主体内容便是一个死循环。

  • 首要获取当时线程的node节点的上个节点
  • 上个节点假如是头节点,测验获取锁;
  • 假如获取到锁,设置当时节点为头节点,并将之前的头节点出队;
  • 假如上述判别有一个为false,履行shouldParkAfterFailedAcquire(p, node)办法
  • 假如shouldParkAfterFailedAcquire办法回来true,则调用park办法阻塞当时线程;
  • 假如回来false,则持续循环。

来看看shouldParkAfterFailedAcquire(p, node)究竟做了什么

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
  • 假如=-1,回来true;
  • 假如>0,则死循环去找到上上个节点(直到waitStatus<=0为止),回来false;
  • 不然将上个节点的waitStatus设置为-1,回来false。

实际上,shouldParkAfterFailedAcquire(p, node)办法便是判别是否需求阻塞当时线程,并修改上个节点的waitStatus的值为-1,表明上个节点的线程处于休眠状况。

最终,咱们来看看真实阻塞的办法parkAndCheckInterrupt()

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

便是调用park办法进行线程的阻塞,也便是说线程履行到这就会开释CPU,停在了这,等候被唤醒。

公正锁

公正锁绝大当地跟非公正锁的实现没啥区别,无非是在CAS获取锁之前判别了一下等候行列中的线程是否轮到了当时线程(hasQueuedPredecessors()办法)

总结

到此,ReentrantLock加锁过程就聊完了,这儿简略做个总结。

  • 首要经过CAS设置state= 1成功便是获取到锁;
  • 失利则再次测验获取锁;
  • 假如仍是失利,则将这个线程节点加入等候行列;
  • 假如这个节点的上个节点是头节点会再次测验获取锁,假如成功即获取锁,并进行节点链表的新旧头节点的收支行列操作;
  • 假如失利则会调用park进行阻塞,等候被唤醒。

文中如有不足之处,欢迎纠正!一同沟通,一同学习,一同生长 ^v^