文章异常啰嗦且绕弯。java
JDK 版本 : OpenJDK 11.0.1node
IDE : idea 2018.3并发
ReentrantLock 是 jdk 中经典的高性能重用锁,做为基础组件常常能在 jdk 的其它并发框架中看到。框架
笔者但愿可以经过此次代码阅读弄懂 AbstractQueueSynchronizer 和 ReentrantLock 的基本构造。ide
本文特指非公平锁的代码实现,对于公平锁暂不作详解。工具
注意,ReentrantLock 在 jdk11 中相比 jdk8 (这样作比较是由于 jdk8 是目前工做中最经常使用的版本) 有了一些代码上的改变,笔者认为代码更加精简了,可是具体性能没有作过实战测试和系统比较。性能
import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; public class LockDemo { //建立一个 lock private ReentrantLock lock = new ReentrantLock(); public void demo(){ //上锁 lock.lock(); //打印当前线程的线程名 System.out.println(Thread.currentThread().getName()); //让线程休眠十秒,在此期间线程不会交出锁,因此其它调用该方法的线程都会阻塞 try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { } //释放锁 lock.unlock(); } //main 方法 public static void main(String[] args) { LockDemo demo = new LockDemo(); //建立两个线程来调用同一个 demo 实例对象,就能看出锁的做用了 new Thread(new LockDemoRunner(demo)).start(); new Thread(new LockDemoRunner(demo)).start(); } } //Runnable 实现类,用于建立线程对象 class LockDemoRunner implements Runnable{ //多个线程对象公用一个 demo 的实例 private LockDemo lockDemo; //构造器 LockDemoRunner(LockDemo demo){ lockDemo = demo;} @Override public void run() { lockDemo.demo(); } }
先来看一下 ReentrantLock 的默认构造器:学习
//ReentrantLock.class public ReentrantLock() { sync = new NonfairSync(); }
再来看一下 ReentrantLock 的加锁和解锁方法:测试
//ReentrantLock.class public void lock() { sync.acquire(1); } //ReentrantLock.class public void unlock() { sync.release(1); }
能够发现,ReentrantLock 的逻辑主要由 sync 对象实现。ui
而 sync 则是一个 NonfairSync 类型的对象。NonfaireSync 是 ReentrantLock 的静态内部类:
//ReentrantLock.class static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; //此方法用于尝试去获取锁 //tryAcquire(...) 原本是 AbstractQueuedSynchronizer 中的方法,此处为重写 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
其实 NonfairSync 的主要逻辑在其父类 Sync 中实现。Sync 一样也是 ReentrantLock 的静态内部类。
Sync 中比较重要的是两个分别用于加解锁的方法:
//Sync.class //此方法用于加锁 @ReservedStackAccess final boolean nonfairTryAcquire(int acquires) { //获取当前线程的线程实例对象 final Thread current = Thread.currentThread(); //获取锁的状态 //c 的初始状态值为 0,意思是还未上锁 int c = getState(); if (c == 0) { //compareAndSetState(...) 方法是 AbstractQueuedSynchronizer 中很是重要的方法 //用以更新锁状态 //此处的 acquires = 1 if (compareAndSetState(0, acquires)) { //保存线程对象 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { //若是当前线程就是正在执行的线程,则将目前的状态值 status 与 acquires 相加,保存为新的 status int nextc = c + acquires; //逻辑上此处是不会为负数的,只用于严谨逻辑 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
另外一个方法:
//Sync.class //此方法用于尝试解锁 @ReservedStackAccess 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; //锁解开以后将保存的当前线程对象置空 setExclusiveOwnerThread(null); } //此处更新状态值 setState(c); return free; }
Sync 中的其它方法主要用于状态判断,都比较简单,再也不累述。
AbstractQueuedSynchronizer 是 jdk 中用来构建同步组件的框架类,是 Sync 的父类。
以前解析 ThreadPoolExecutor 的内部类 Worker 的时候也初步了解过。
在上述探索 Sync 的过程当中用到了不少此类中的方法。因此对用到的方法作一个追踪。
AbstractQueuedSynchronizer 中最重要的一个状态控制变量是 status,它表明锁目前是被使用仍是空闲:
//AbstractQueuedSynchronizer.class private volatile int state;
获取和存入 status 的值:
//AbstractQueuedSynchronizer.class protected final int getState() { return state; } //AbstractQueuedSynchronizer.class protected final void setState(int newState) { state = newState; }
以上的 set/get 方法都很简单和常规,可是实际上 status 的存值主要使用该类中的另外一个方法:
//AbstractQueuedSynchronizer.class protected final boolean compareAndSetState(int expect, int update) { //compareAndSet(...) 这个方法在使用 AtomicInteger 的时候接触过,可是此处的 STATE 并非 AtomicInteger //STATE 是一个定义在 AbstractQueuedSynchronizer 中的 VarHandle 类型的变量 //VarHandle 是 jdk9 中新增的一个并发工具,目前网上对此工具的分析比较少 //基本能判断的是,此工具的做用与 AtomicXXXX 工具类很相似,能提供原子化的操做,并在必定程度上替代 Unsafe //此方法用于先比较该实例对象中的 status 是否与第二个参数的值相等,若是是,则将 status 替换成第三个参数的值,返回 true //若是不相等,就不进行替换,并返回 false return STATE.compareAndSet(this, expect, update); }
VarHandler 的实例化也比较神奇,能够作一下了解:
//定义的是 static 对象,能够被反复利用,而不是 AtomicXXXX 的模式了 private static final VarHandle STATE; private static final VarHandle HEAD; private static final VarHandle TAIL; static { try { //VarHandler 的实例化工厂 MethodHandles.Lookup l = MethodHandles.lookup(); //实例化的时候将类 class、参数的名称、参数的类型 传入,就能够建立一个指向此参数的 VarHandler 实例对象了 STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class); HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class); TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } //LockSupport 是一个服务于锁的静态工具类 Class<?> ensureLoaded = LockSupport.class; }
AbstractQueuedSynchronizer 有一个静态内部类 Node,本质上是一个双向链表的节点对象。
同时 AbstractQueuedSynchronizer 还有两个 Node 节点对象:
//双向链表的头结点 private transient volatile Node head; //双向链表的尾节点 private transient volatile Node tail;
每一个 Node 节点对象的内部储存有一个 Thread 对象,即为等待执行的线程的实例化对象:
//Node.class //此为 Node 的构造方法,传入的 nextWaiter 即为该节点的后一个节点 Node(Node nextWaiter) { this.nextWaiter = nextWaiter; //THREAD 是一个 VarHandler 对象,用于将线程对象存入当前节点中 //注意,当前线程被存入了当前的 Node,而不是下一个 Node THREAD.set(this, Thread.currentThread()); }
再来看一个增长 Node 的方法:
//AbstractQueuedSynchronizer.class private Node addWaiter(Node mode) { //此时当前线程对象已经被存入 node 对象中 Node node = new Node(mode); //比较见名知意,因此不展开细讲了,只稍微说起 //这个 for 循环用于将 node 对象添加到链表的尾部,代替掉以前的 tail 对象 //有一种特殊状况,即 oldTail 是 null,则表明着该链表实际上是空的,没有任何节点 //这种状况下调用 initializeSyncQueue() 方法初始化链表,即此时 node 对象既是头节点也是尾节点 for (;;) { Node oldTail = tail; if (oldTail != null) { node.setPrevRelaxed(oldTail); if (compareAndSetTail(oldTail, node)) { oldTail.next = node; return node; } } else { initializeSyncQueue(); } } }
来看 ReentrantLock 的加锁方法:
//ReentrantLock.class public void lock() { sync.acquire(1); }
acquire(...) 方法在 AbstractQueuedSynchronizer 中实现:
//AbstractQueuedSynchronizer.class public final void acquire(int arg) { //此处的判断条件处,会先调用 tryAcquire(...) 方法去尝试获取锁的使用权 //若是获取成功,此处 tryAcquire(...) 方法会返回 true,那么 !tryAcquire(...) = false,此方法会直接结束 //若是获取失败,此时 !tryAcquire(...) = true,进入 acquireQueued(...) 方法 //在执行 acquireQueued(...) 方法以前,会先执行 addWaiter(...) 方法,此时当前线程已经被存入尾部节点中 //Node.EXCLUSIVE 是一个 null 对象 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //selfInterrupt() 会中断当前线程,使得线程处于等待被唤醒的状态 selfInterrupt(); }
再来追踪一下 acquireQueued(...) 方法:
//AbstractQueuedSynchronizer.class final boolean acquireQueued(final Node node, int arg) { //在本例中此时传入的 node 是链表的尾节点,且是存储了当前线程的节点对象 //arg = 1 boolean interrupted = false; try { for (;;) { //获取当前节点的前一个节点 final Node p = node.predecessor(); //若是 p 节点是头节点,且当前线程尝试获取锁的使用权成功 //则让当前节点成为头节点,并删去原先的头节点(即 p 节点) if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; return interrupted; } //shouldParkAfterFailedAcquire(...) 方法会根据 node 的前一节点的状态来判断该节点是否要被挂起或者唤醒 //parkAndCheckInterrupt(...) 内部会调用 unsafe 的相关方法挂起节点 if (shouldParkAfterFailedAcquire(p, node)) interrupted |= parkAndCheckInterrupt(); } } catch (Throwable t) { cancelAcquire(node); if (interrupted) selfInterrupt(); throw t; } }
因而可知,当前节点 node 的最终处理方式是由其的前一节点的状态来肯定的。Node 内置了几种状态值:
//CANCELLED 表明该节点处于取消状态,该节点不会执行了 static final int CANCELLED = 1; //SIGNAL 表明该节点的下一节点处于阻塞状态,会以后被执行 static final int SIGNAL = -1; //CONDITION 表明该节点处于阻塞状态 static final int CONDITION = -2; //PROPAGATE 表明共享状态 static final int PROPAGATE = -3; //还有一种状态 0,即为节点的初始状态
了解了状态以后再来看 shouldParkAfterFailedAcquire(...) 方法:
//AbstractQueuedSynchronizer.class private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { //获取 node 节点的前节点的状态 int ws = pred.waitStatus; //若是是 SIGNAL,直接返回 true,而后 node 节点会进入被挂起 if (ws == Node.SIGNAL) return true; //状态值大于 0,只多是 CANCELLED 状态,即此节点已经被废弃了 if (ws > 0) { //不断往前遍历,将中间被废弃的节点所有剔除出链表中 do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //若是状态值并不是 CANCELLED 或者 SIGNAL,在这里会将状态值修改为 SIGNAL pred.compareAndSetWaitStatus(ws, Node.SIGNAL); } //只要 pred 的状态值不为 SIGNAL,都会返回 false return false; }
shouldParkAfterFailedAcquire(...) 方法和 parkAndCheckInterrupt(...) 方法共同做用,对不符合的节点进行剔除,对符合要求的节点进行挂起操做。
这样一来节点所封装的线程也就进入了阻塞队列中,等待被锁唤醒。
回到 ReentrantLock 的解锁代码:
//ReentrantLock.class public void unlock() { sync.release(1); }
release(...) 在 AbstractQueuedSynchronizer 中实现:
//AbstractQueuedSynchronizer.class public final boolean release(int arg) { //此处的 tryRelease(...) 是 Sync 中重写以后的方法,具体看上述 Sync 的实现 //此方法修改了当前锁的状态值 if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
代码和上一 part 的比较相似,重点来看一下 unparkSuccessor(...) 方法:
//AbstractQueuedSynchronizer.class private void unparkSuccessor(Node node) { //获取 node 的状态值 int ws = node.waitStatus; //若是状态值为 SIGNAL、CONDITION、PROPAGATE 的话就会转成 0 if (ws < 0) node.compareAndSetWaitStatus(ws, 0); //获取 node 的下一节点 s Node s = node.next; //若是 s 节点不存在,或者已经被废弃了,就会一直轮询,找到一个符合条件的 if (s == null || s.waitStatus > 0) { s = null; for (Node p = tail; p != node && p != null; p = p.prev) if (p.waitStatus <= 0) s = p; } //将 s 节点激活 //注意此处的 s 节点为 node 的下一个可以被使用的节点 if (s != null) LockSupport.unpark(s.thread); }
unparkSuccessor(...) 方法的核心是选择符合要求的下一节点,并将其所表明的线程对象从挂起状态唤醒。
注意,此处展现的是非公平锁的逻辑。非公平锁讲求先到先得,会依次唤醒线程并执行代码块。公平锁则一次性唤醒因此线程并进行一次公平争夺。
LockSupport 的 park(...) 方法用于挂起线程:
//LockSupport.class public static void park(Object blocker) { //获取当前线程对象 Thread t = Thread.currentThread(); //blocker 在本例中即为锁对象自己,setBlocker(...) 方法主要是作一下记录,此线程是被谁阻塞了 setBlocker(t, blocker); //挂起线程 U.park(false, 0L); //当线程执行到这句代码的时候,说明线程已经从挂起状态被唤醒了 //因此这里能够清空掉记录 setBlocker(t, null); }
继续追踪 setBlocker(...) 方法:
//LockSupport.class private static void setBlocker(Thread t, Object arg) { //putObject(...) 方法会把一个对象存到指定的地址处 //此例的 t 是当前的线程对象,PARKBLOCKER 是一个 long 类型的内存地址,arg 是锁对象自己 U.putObject(t, PARKBLOCKER, arg); }
能够看到,park(...) 方法和 setBlocker(...) 方法都是调用了 U 对象的相关方法。
U 对象是一个 Unsafe 实例:
//LockSupport.class private static final Unsafe U = Unsafe.getUnsafe();
Unsafe 的 park(...) 方法用于挂起线程,putObject(...) 方法用于存入对象。
//LockSupport.class public static void unpark(Thread thread) { //唤醒线程 if (thread != null) U.unpark(thread); }
总结一下重入锁的业务逻辑:
1 当第一个线程进入到锁区域(即调用 lock() 方法)的时候,会被锁记录为当前线程,而且修改锁的状态值 2 当有其它线程进入到该代码块,可是锁的状态值并非初始值(即以前的线程未释放锁资源)的时候,线程会被封装成节点并存入到链表的末尾,而后被挂起 3 以前的线程释放锁资源(即调用 unlock() 方法)的时候,锁会去遍历内部的链表,唤醒下一个符合要求的线程(特指非公平锁)
本文仅为我的的学习笔记,可能存在错误或者表述不清的地方,有缘补充。