上面这段代码是最简单的获取ReentrantLock的非公平锁的代码,我们来看看这段代码后面的源码是如何运行的。
final void lock() { //首先尝试看看能不能获取到锁,如果CAS成功,那么就获取到了 if (compareAndSetState(0, 1)) //如果获取到了,就记录一下当前的线程,以便后面的重入 setExclusiveOwnerThread(Thread.currentThread()); else //如果CAS失败,则调用acurire方法 acquire(1); } public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg) selfInterrupt(); }这边的话首先会再尝试获取锁,如果获取到了,返回true,就会退出if语句,如果没有获取到,那么则将当前线程添加到队列中,并循环获取锁,直到获取到为止。
下面是tryAcquire方法的源码:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); //获取到锁的标志状态 int c = getState(); if (c == 0) { //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; }如果尝试获取锁失败了,则先会将当前线程添加到一个队列中:
private Node addWaiter(Node mode) { //使用当前线程创建一个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; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //如果是第一次构建队列,则调用该方法 enq(node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize //如果尾节点还是空的,那么构建一个空节点做为头节点 //然后在下一次循环的时候进入到else if (compareAndSetHead(new Node())) tail = head; } else { //和上面一样,将当前线程构建的节点添加到队列的尾部 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }到这里,队列已经构建完成了,那么这个队列中的节点在有锁释放后,又是怎么去争夺锁资源的呢?
final boolean acquireQueued(final Node node, int arg) {、 //标识获取锁的过程中是否出现了异常 boolean failed = true; try { //标识线程在等待唤醒的时候是否被打断(interrupt) boolean interrupted = false; //这里会循环获取锁,知道获取到或者出现异常 for (;;) { final Node p = node.predecessor(); //如果当前线程为第一个节点(排除head节点),则尝试获取锁 if (p == head && tryAcquire(arg)) { //如果获取到了,则将当前节点设置为head setHead(node); p.next = null; // help GC failed = false; return interrupted; } //判断节点是否需要park,如果需要,则进入park if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }这段代码有一点比较难理解,就是首先会判断当前线程的前一个节点是否为head节点,如果是的话,才会尝试获取锁。但是非公平锁不是应该所有的节点都去争夺锁资源吗?其实非公平是作用于新入的节点,而已经调用过addWaiter方法的节点,则需要排队。
PS:park方法与wait方法的作用类似。
下面我们再来看一下ReentrantLock是如何来判断当前线程获取锁失败后是否需要进入到park状态。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) //如果前节点是SIGNAL状体,则代表需要park return true; if (ws > 0) { //如果waitStatus的值大于0,代表已取消,需要将无效的节点删除 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //否则将前节点设置为SIGNAL状态 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }上面的代码很好理解,但是需要知道Node的waitStatus的几种状态的含义。
CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。0状态:值为0,代表初始化状态。下面再来看看parkAndCheckInterrupt方法:
private final boolean parkAndCheckInterrupt() { //通过LockSupport类来park该线程 LockSupport.park(this); //将park的线程唤醒可能是调用unpark方法,也可能是被打断了 return Thread.interrupted(); }到这里,非公平锁的整个获取流程就结束了。
通过上面的源码解析,我们已经能够大致了解ReentrantLock是如何实现非公平锁的了,下面我们来个小结。
这里有一点需要注意,就是在最后的时候,如果线程获取到了锁,并且中途被打断过,这个时候线程会调用interrupte(),但是这个是没有任何意义的,因为运行中的线程是不能被打断的。 当然,ReentrantLock也是支持被打断的,具体可参考:
基于上面的内容,我们已经理解了ReentrantLock非公平锁的实现原理,其实公平锁和非公平锁的源码差不多,只有两处不一样。 第一处:
final void lock() { acquire(1); }这里就是调用lock方法的入口,和非公平锁不一样的是非公平锁会先去尝试通过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; }这段代码是尝试去获取锁,可以看出,这边当state为0的时候,也就是无锁状态的时候,会先去判断当前线程的是否构建了节点且节点是否为head后的第一个节点,如果是,才会去尝试CAS获取锁。
public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; //只有当没有任何节点获取锁或者本节点为head后第一个节点 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }从这里我们也能看出公平锁和非公平锁的区别:公平锁的新入线程不能直接获取锁,必须去排队(除非没有任何竞争发生)。而非公平锁新入的线程则可以先尝试获取锁,如果失败了再排队。