JDK1.8源码解析之ReentrantLock(可重入锁)
ReentrantLock源码解析
-
前言
在学习阻塞队源码的时候,比如ArrayBlockingQueue、LinkedBlockingQueue、CyclicBarrier、SynchronousQueue频繁的遇到ReentrantLock,故先把ReentrantLock原理了解,是学习阻塞队列的基础。对Doug Lea大师崇拜。 -
类继承关系
有一个特别重要的抽象静态内部类Sync,这个类实现了AbstractQueueSynchronizer(提供锁模板,这篇文章中用到时候在分析)。 -
ReentrantLock有两种模式,1 公平锁 2 公平锁
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();// 初始化的时候可以指定,不指定话默认是非公平模式 }
非公平锁
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() {// 重写Sync的lock方法,自定义非公平锁的实现逻辑,父类相当与模板,模板实现了大部分的方法,子类只需实现自己的逻辑。 if (compareAndSetState(0, 1)) // 这里是体现非公平锁的关键,申请锁的时候先直接尝试修改AQS的锁标志(非常的强硬) setExclusiveOwnerThread(Thread.currentThread());// 成功修改锁标志位 则设置当前独占锁的线程 else acquire(1);// 这里走的是AQS的模板方法 尝试获取 } protected final boolean tryAcquire(int acquires) { // 尝试获取锁 NonfairSync类定义 return nonfairTryAcquire(acquires); } }
来看看AQS的acquire
public final void acquire(int arg) { if (!tryAcquire(arg) && // 执行子类自定义的方法 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 申请失败情况下 将当前线程添加到对列中 selfInterrupt(); }
来看看NonfaireSync的tryAcquire
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 获取AQS锁状态 if (c == 0) {// 当前锁处于空闲状态 if (compareAndSetState(0, acquires)) {// unsafe设置锁状态 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {// 这里体现了ReentrantLock可重入的性质,同一个线程可以多次获取锁 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false;// 尝试获取锁失败 }
所获取失败,则会将当前线程加入到阻塞队列,来看看AQS的acquireQueued 是如何实现的?
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)) {// 等待队列的head释放了锁 setHead(node);// 将当前节点设置成head p.next = null; // help GC // 自己的next指向自己方便gc回收 failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) &&// 获取锁失败后开始进入真正的阻塞 parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
来看看AQS的shouldParkAfterFailedAcquire是如何实现的?
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 只有当前节点的前驱节点的状态是signal的才可以parking,就是告诉前驱我准备好了,你执行完记得唤醒我 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; }
接下来再看看parkAndCheckInterrupt 这个方法就是用unsafe的park,使当前线程阻塞,等待unpark。
到此整个获取锁的过程结束了,我的天感觉脑子不够用了。
既然这里park了,那在哪里unpark这个线程呢?来看看ReentrantLock 的unlock,所以在使用lock的地方必须要以unlock结束,lock几次就需要unlock几次,将AQS的state恢复至0,要不然等待的线程永远得不到唤醒。
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) // h不等于null 说明等待队列不为空 unparkSuccessor(h);// 唤醒后继节点 return true; } return false; } 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;// 直到AQS状态为0,才算完成锁的释放 setExclusiveOwnerThread(null); } setState(c); return free; }
公平锁
了解了非公平锁,公平锁就超级简单了,唯一不同的就是tryAcquire了,来看看?
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; }
ReentrantLock还有一个强大的特性,一个lock对象可以创建多个条件,每个条件也是可以阻塞的,这样就使ReentrantLock更加灵活,而Synchronized只能对
3ff8
一个类或者一个对象加锁。下面来看看Condition。
Condition是一个接口
总共只有两类方法,await类的方法是将任务阻塞到条件队列中,signal方法是将任务从条件队列中转移到lock的等待队列中。
AQS中的内部类ConditionObject实现了Condition接口,先看成员变量,
/**条件队列的第一个等待线程*/ private transient Node firstWaiter; /** 条件队列的最后一个等待线程 */ private transient Node lastWaiter;
条件队列就是一个单向链表,链表中的节点用的依旧是AQS中的节点,创建新节点时,将新节点设置为lastWaiter的next,完成链接。
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); Node node = addConditionWaiter();// 将任务添加到条件队列的尾部 int savedState = fullyRelease(node);// 这里将AQS锁的状态清除掉,savedState是保存await之前node的状态,因为在唤醒之后需要执行unlock操作,这里也体现了可重入锁, int interruptMode = 0; while (!isOnSyncQueue(node)) {// 只有await获得signal后才会将node移动到lock的等待队列中。 LockSupport.park(this);// 执行阻塞,等待signal 的transfer if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } if (acquireQueued(node, savedState) && interruptMode != THROW_IE)// 走到这里就说明node已经从条件队列中转移到等待队列中,现在可以尝试获取lock锁 interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
接下来看看AQS ConditionObject 的signal,signal的目的是将条件队里中的头结点转移动等待队列中。
public final void signal() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignal(first); } private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; Node p = enq(node);// 把任务添加到等待队列 int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread);// 唤醒阻塞线程,回到await方法,继续执行,接下来进行尝试获取lock锁,这里就和等待队列中尝试获取锁一样了。 return true; }
condition这块核心的部分分析完了,其他的方法,基本上没啥难点了,下一篇开启阻塞队列之旅了。
- JUC之ReentrantReadWriteLock(JDK1.8源码)
- 【JUC】JDK1.8源码分析之ReentrantLock
- 【JUC】JDK1.8源码分析之ReentrantLock(三)
- jdk源码解读-并发包-Lock-ReentrantReadWriteLock(1)-整体介绍以及读锁的lock 和 unlock 解析
- HashMap源码解析(jdk1.8)
- JDK1.8 --- ArrayList-LinkedList源码解析
- jdk 源码分析(17)java Semaphore 源码解析及与lock对比
- Java源码解析之可重入锁ReentrantLock
- jdk 源码分析(9)java ReentrantReadWriteLock分析
- 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁
- BlockingQueue源码解析jdk1.8
- jdk源码解读-并发包-Lock-ReentrantLock(1)--lock()与unlock()方法走读
- JDK1.8源码解析之ConcurrentHashMap
- JDK1.8源码解析之SynchronousQueue
- 【JUC】JDK1.8源码分析之LockSupport(一)
- JDK1.8源码解析——java.util.ArrayList 类(数组实现)
- ReentrantLock源码解析
- JDK1.8源码解析之 HashMap
- HashMap 源码解析(JDK1.8)
- ReentrantLock 源码解析