Lock reentrantLock = new ReentrantLock(true); reentrantLock.lock(); //加锁 try{ // todo } finally{ reentrantLock.unlock(); // 释放锁 }
在new ReentrantLock(true)的时候加入关键字truejava
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
当传入的参数值为true的时候建立的对象为new FairSync()公平锁。node
reentrantLock.lock(); //加锁
加锁的实际调用的方法是建立的公平锁里面的lock方法并发
static final class FairSync extends Sync { final void lock() { acquire(1); } ... }
代码中的acquire方法和非公平锁中的acquire方法同样都是调用的AQS中的final方法ide
## AbstractQueuedSynchronizer public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
不过不一样之处是这里面的tryAcquire(arg)方法是调用的公平锁里面实现的方法工具
这个方法其实和非公平锁方法特别类似,只有一处不一样公平锁中含有一个特殊的方法叫作hasQueuedPredecessors()该方法也是AQS中的方法,该方法的实质就是要判断该节点的前驱节点是不是head节点ui
## AbstractQueuedSynchronizer public final boolean hasQueuedPredecessors() { Node t = tail; Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
剩下的部分和前一篇分析的非公平锁几乎是一个流程this
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; } } // 若是是当前线程持有的锁信息,在原来的state的值上加上acquires的值 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // 设置state的值 setState(nextc); // 获取锁成功 return true; } // 获取锁失败了才返回false return false; }
注意一下只有当返回false的时候才是tryAcquire失败的时候。此时就会走到繁琐的addWaiter(Node.EXCLUSIVE)方法线程
若是前面tryAcquire失败就会进行接下来的addWaiter(Node.EXCLUSIVE)3d
## AbstractQueuedSynchronizer private Node addWaiter(Node mode) { // 建立一个新的node节点 mode 为Node.EXCLUSIVE = null Node node = new Node(Thread.currentThread(), mode); // 获取尾部节点 Node pred = tail; // 若是尾部节点不为空的话将新加入的节点设置成尾节点并返回当前node节点 if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 若是尾部节点为空整明当前队列是空值得须要将当前节点入队的时候先初始化队列 enq(node); return node; }
enq(node)方法是节点入队的方法咱们来分析一下,enq入队方法也是AQS中的方法,注意该方法的死循环,不管如何也要将该节点加入到队列中。指针
## AbstractQueuedSynchronizer private Node enq(final Node node) { for (;;) { Node t = tail; // 若是尾节点为空的话,那么须要插入一个新的节点当头节点 if (t == null) { if (compareAndSetHead(new Node())) tail = head; } else { // 若是不为空的话,将当前节点变为尾节点并返回当前节点的前驱节点 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
其实和非公平锁的addWaiter(Node node)是同样的流程,分析完。
此时当前节点已经被加入到了阻塞队列中了,进入到了acquireQueued方法。该方法也是AQS中的方法。
## AbstractQueuedSynchronizer final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // 获取当前节点的前驱节点 final Node p = node.predecessor(); // 若是当前节点的前驱节点是头节点的话会再一次执行tryAcquire方法获 // 取锁 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); } }
注意 setHead(node) 中具体的实现细节thread为null,prev也为null其实就是若是当前节点的前驱节点为头节点的话,那么当前节点变成了头节点也就是以前阻塞队列的虚拟头节点。
private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
若是不是头节点或者tryAcquire()方法执行失败执行下面的更加繁琐的方法shouldParkAfterFailedAcquire(p, node),若是该方法返回true才会执行到下面的parkAndCheckInterrupt()方法,这两个方法都是AQS中的方法。
## AbstractQueuedSynchronizer private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 获取前驱节点的状态 int ws = pred.waitStatus; // 若是前驱节点的状态为SIGNAL那么直接就能够沉睡了,由于若是一个节点要是进入 // 阻塞队列的话,那么他的前驱节点的waitStatus必须是SIGNAL状态。 if (ws == Node.SIGNAL) return true; // 若是前驱节点不是Node.SIGNAL状态就往前遍历一值寻找节点的waitStatus必须 // 是SIGNAL状态的节点 if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { // 若是没有找到符合条件的节点,那么就将当前节点的前驱节点的waitStatus // 设置成SIGNAL状态 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
若是返回的值是false,就要注意此时又继续进入了下一次死循环中,由于若是往前遍历的过程当中有可能他的前驱节点变成了头节点,那么就能够再次的获取锁,若是不是的话那么只能
执行parkAndCheckInterrupt()方法进行线程的挂起了。
private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
不管如何最终都走到了cancelAcquire方法
private void cancelAcquire(Node node) { if (node == null) return; node.thread = null; Node pred = node.prev; // 跳过全部取消请求的节点 while (pred.waitStatus > 0) node.prev = pred = pred.prev; Node predNext = pred.next; // 将当前节点设置成取消状态,为了后续遍历跳过咱们 node.waitStatus = Node.CANCELLED; // 若是当前节点是尾节点,而且将当前节点的前驱节点设置成尾节点成功 if (node == tail && compareAndSetTail(node, pred)) { // 当前节点的前驱节点的后续节点为空 compareAndSetNext(pred, predNext, null); } else { int ws; // 若是前驱节点不是头节点 if (pred != head && // 前驱节点的状态是Node.SIGNAL或者前驱节点的waitStatus设置 // 成Node.SIGNAL ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && // 前驱节点的thread 不为空 pred.thread != null) { // 获取当前节点的后继节点 Node next = node.next; // 若是后继节点不为空,而且后继节点waitStatus 小于0 if (next != null && next.waitStatus <= 0) // 将当前节点的后继节点设置成当前节点的前驱节点的后继节点 compareAndSetNext(pred, predNext, next); } else { // 若是上面当前节点的前驱节点是head或者其余条件不知足那么就唤醒当前节点 unparkSuccessor(node); } node.next = node; // help GC } }
unparkSuccessor(node)唤醒当前节点,该方法也是AbstractQueuedSynchronizer中的方法
private void unparkSuccessor(Node node) { // 获取当前节点的状态 int ws = node.waitStatus; // 若是当前节点状态小于0那么设置成0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取当前节点的后继节点 Node s = node.next; // 若是后继节点为空,或者后继节点的状态小于0 if (s == null || s.waitStatus > 0) { // 后继节点置为null。视为取消请求的节点 s = null; // 获取尾节点,而且尾节点不为空,不是当前节点,那么就往前遍历寻找 // 节点waitStatus 状态小于0的节点赋予给当前节点的后继节点 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) // 唤醒后继节点 LockSupport.unpark(s.thread); }
流程图和上一篇非公平锁的获取流程图十分类似只有一点点区别这里就不过多的描述了。
尝试释放此锁。若是当前线程是此锁的持有者,则保留计数将减小。 若是保持计数如今为零,则释放锁定。 若是当前线程不是此锁的持有者,则抛出IllegalMonitorStateException。
## ReentrantLock public void unlock() { sync.release(1); }
sync.release(1) 调用的是AbstractQueuedSynchronizer中的release方法
## AbstractQueuedSynchronizer public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
分析tryRelease(arg)方法
tryRelease(arg)该方法调用的是ReentrantLock中
protected final boolean tryRelease(int releases) { // 获取当前锁持有的线程数量和须要释放的值进行相减 int c = getState() - releases; // 若是当前线程不是锁占有的线程抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 若是此时c = 0就意味着state = 0,当前锁没有被任意线程占有 // 将当前所的占有线程设置为空 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 设置state的值为 0 setState(c); return free; }
若是头节点不为空,而且waitStatus != 0,唤醒后续节点若是存在的话。
这里的判断条件为何是h != null && h.waitStatus != 0?
由于h == null的话,Head还没初始化。初始状况下,head == null,第一个节点入队,Head会被初始化一个虚拟节点。因此说,这里若是还没来得及入队,就会出现head == null 的状况。
private void unparkSuccessor(Node node) { // 获取头结点waitStatus int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取当前节点的下一个节点 Node s = node.next; //若是下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点 if (s == null || s.waitStatus > 0) { s = null; // 就从尾部节点开始找往前遍历,找到队列中第一个waitStatus<0的节点。 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 若是当前节点的下个节点不为空,并且状态<=0,就把当前节点唤醒 if (s != null) LockSupport.unpark(s.thread); }
为何要从后往前找第一个非Cancelled的节点呢?
看一下addWaiter方法
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; }
咱们从这里能够看到,节点入队并非原子操做,也就是说,node.prev = pred, compareAndSetTail(pred, node) 这两个地方能够看做Tail入队的原子操做,可是此时pred.next = node;还没执行,若是这个时候执行了unparkSuccessor方法,就没办法从前日后找了,因此须要从后往前找。还有一点缘由,在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,所以也是必需要从后往前遍历才可以遍历彻底部的Node。
因此,若是是从前日后找,因为极端状况下入队的非原子操做和CANCELLED节点产生过程当中断开Next指针的操做,可能会致使没法遍历全部的节点。因此,唤醒对应的线程后,对应的线程就会继续往下执行。
下一篇讲解并发工具包下的LockSupport,谢谢你们的关注和支持!有问题但愿你们指出,共同进步!!!