Java 并发 AQS 重入锁

实现重入锁ReentrantLock锁使用到的技术

  1. CAS 保证操作原子性
  2. AQS 带有头尾节点的队列 链表实现
Node {
//Node代表了等待的线程
  Node prev 前一个Node
  Node next 后面一个Node
}

AQS{
Node head 头节点
Node tail 尾结点
}
  1. 自旋操作

用到的一些方法的含义(公平锁实现情况)(代码为Open-JDK13中的)

  1. tryAcquire 本线程尝试获取锁,获取成功返回true
  2. hasQueuedPredecessors 判断本线程是否需要排队,需要排队返回true
  3. addWaiter 往队列中添加一个Node 即添加一个排队的线程

源码具体执行流程
步骤一:

上锁.png

步骤二:
获取锁.png

步骤三:
获取锁具体过程.png

这里开始麻烦起来了,第一步本线程第一次尝试获取锁,调用 tryAcquire, 如果获取成功,则上锁过程结束,如果获取失败就执行另外两步。
先看下tryAcquire干什么了(为公平锁实现的tryAcquire)

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            获取锁的状态
            int c = getState();
            如果状态为0,说明此时锁时空闲的
            但是注意可能有多个线程同时检测到锁空闲,那么就会有多个线程都进入下面的IF
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
         ... 省略一部分代码,先不讨论
            return false;
        }

如果是空闲状态,那么就要先看看是不是要排队(因为可能有多个线程同时,非公平锁就不用检测要不要排队)

    public final boolean hasQueuedPredecessors() {
        Node h, s;     h表示头结点,s表示后续节点
        如果h==null 那么就说明此此时队列都没创建 本线程是第一个线程,说明没有其他线程,就**不用排队**
        if ((h = head) != null) {
            s==null说明下一个没有 但也可能因为并发的原因 不能保证后面也是空的
            s.waitStatus>0 说明下一个被取消了,要看后面的是什么情况
            if ((s = h.next) == null || s.waitStatus > 0) {
                s = null; // traverse in case of concurrent cancellation
                for (Node p = tail; p != h && p != null; p = p.prev) {   从后往前找
                    找到一个不是null的也不是被取消的
                    也就是找到队列中第一个真正在等待的
                    if (p.waitStatus <= 0)
                        s = p;
                }
            }
            不用排队情况(人即线程)
                情况一:根本没有在等待,前面可能有人,也可能没有人
                情况二:有在等待的,但是等待的人是自己,前面肯定有人
            要排队的情况
                有在真正等待的,并且第一个真正在等待的不是自己
              s==null 就说明没有等待的   **不用排队** 队列已经初始化过了
              s!=null就说明有等待的,
                    等待的人不是自己    **要排队**
                    等待的人是自己   **不用排队** 这里会比较困惑,因为这个情形的出现要结合后面的**acquireQueued方法来看**
                  因为如果你是第一个等待者,那么acquireQueued会再调用一次tryAcquire,而tryAcquire会再调用一次hasQueuedPredecessors,
                  那么这种情形就会出现了即等待的人是自己
            if (s != null && s.thread != Thread.currentThread())
                return true;
        }
        return false;
    }

这个判断排队比较绕。。。
然后就是回到了之前的tryAcquire了,如果不需要排队,那么直接用CAS操作尝试修改锁的状态,如果修改成功那么获取锁就成功了。
如果不需要带队,但是获取锁失败了那就说明实际上还是需要排队的,因为这种情况就说明了存在多线程竞争
如果需要排队就要调用addWaiter往队列中添加等待者,即排队,这个方法内部就不展开了,就是入队,它用CAS操作保证了入队顺序是线程安全的。 注意排好队后,线程还没有阻塞。
排好队后队列中排队的人看看自己能不能获取锁,如果不能就阻塞线程,直到被唤醒

    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这个方法也是比较令人困惑的地方,也是我自己理解还不到位的地方

这个方法其实会执行两遍,第一遍是把前面等待者(如果有的话)的等待状态设置好(设置成阻塞等待)
第二遍再确定自己是否需要阻塞
如果你都因为获取锁失败
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
      前面一个等待者的等待状态
        int ws = pred.waitStatus;
        说明前面一个已经在阻塞等待了,那么自然我也就要阻塞等待了
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
             你前面的等待者(等待线程)可能被取消了 找到一个没有被取消的
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            第一次执行 把前面的等待者状态设置好,因为它肯定已经阻塞等待
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }

这里说的比较乱。。。
为什么要后面的人来设置前面的人的阻塞状态呢,因为一个线程真正阻塞了,那么它的阻塞状态才能设置,即先有阻塞,然后有阻塞状态,但是阻塞的线程就根本不能动了,自然也不能设置阻塞状态。
拿睡觉来打个比方,你睡着还是没睡着不能由你说了算,你不可能告诉别人你睡着了,只能由别人来看,只有别人才能看到你是否真正睡着了。
并且这段有解锁过程相关性挺高的,以后有时间再说了。


这篇文章主要是为了记录自己在学习AQS时候的一些感受,也方便以后自己复习。所以用到的语言也不够准确,过程不能保证正确。如果有同学看到这篇文章,一定要带着怀疑的心态去看。如果发现错误的地方希望能批评指出。

你可能感兴趣的:(Java 并发 AQS 重入锁)