ReentrantLock源码分析
一、ReentrantLock介绍
ReentrantLock是JDK1.5引入的,实现Lock接口的互斥锁。保证多线程的环境下,共享资源的原子性。与Synchronized的非公平锁不同,ReentrantLock的实现公平锁、非公平锁。ReentrantLock是重入锁,重入是指,同一个线程可以重复执行加锁的代码段。
二、ReentrantLock使用
ReentrantLock功能与Synchronized一样,使用起来比Synchronized更加灵活。与Synchronized自动加锁、释放锁不同,ReentrantLock需要手动加锁、释放锁。使用示例如下:
1 import java.util.concurrent.locks.ReentrantLock; 2 3 public class TestReentrantLock { 4 public static void main(String[] args) { 5 ReentrantLock lock = new ReentrantLock(); 6 lock.lock(); 7 try{ 8 // todo 9 }finally { 10 lock.unlock(); 11 } 12 } 13 }
三、ReentrantLock原理
Synchronized基于对象实现;ReentrantLock基于 AQS + CAS 实现。
3.1、lock()流程图
ReentrantLock基于抽象队列同步器AQS + CAS 实现的加锁、释放锁。ReentrantLock实现了公平锁、非公平锁,公平锁与非公平锁唯一的区别在于,非公平锁不会判断等待队列中是否节点等待获取锁,而是直接尝试获取锁,获取不到,再将当前线程节点添加进等待队列的尾节点,判断当前线程节点是否挂起。
3.2、unlock()流程图
四、ReentrantLock源码分析
1、构造函数
1 private final Sync sync; 2 3 // 默认使用非公平锁 4 public ReentrantLock() { 5 sync = new NonfairSync(); 6 } 7 8 // fair=true,公平锁;否则,非公平锁 9 public ReentrantLock(boolean fair) { 10 sync = fair ? new FairSync() : new NonfairSync(); 11 }
Sync是ReentrantLock的抽象静态内部类,继承自AQS(AbstractQueuedSynchronizer) - 抽象队列同步器,AQS中定义了锁的基本行为,AQS中用volatile修饰的state表示当前锁重入的次数。
01 Sync类图:TODO
NonfairSync、FairSync是ReentrantLock的静态内部类,继承ReentrantLock$Sync,NonfairSync实现非公平锁,FairSync实现公平锁。
2、加锁
1、lock()
1 private final Sync sync; 2 3 // 加锁 4 public void lock() { 5 sync.lock(); 6 }
1.1、公平锁
调用AQS的acquire方法。ReentrantLock$FairSync#lock() 核心代码:
// 加锁 final void lock() { acquire(1); }
1.2、非公平锁
通过CAS尝试获取锁(将AQS的state由0修改为1),若成功,代表当前线程获取锁资源成功;若失败调用AQS的acquire方法。ReentrantLock$NonfairSync#lock() 核心代码:
1 // 加锁 2 final void lock() { 3 // 获取锁资源,CAS 修改 AQS 的 state 属性值,,获取成功,设置当前线程 4 if (compareAndSetState(0, 1)) 5 setExclusiveOwnerThread(Thread.currentThread()); 6 // 获取失败,执行AQS的acquire 7 else 8 acquire(1); 9 }
2、acquire()
acquire()方法是Sync父类AQS中的方法,AbstractQueuedSynchronizer#acquire() 核心代码:
1 // 获取锁资源 2 public final void acquire(int arg) { 3 // 尝试获取锁资源 4 if (!tryAcquire(arg) && 5 // 当前线程为获取到锁资源,加入等待队列,同时挂起线程,等待唤醒 6 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 7 selfInterrupt(); 8 }
1、调用tryAcquire方法:
2、调用addWaiter方法
3、调用acquireQueued方法
2.1、tryAcquire()
tryAcquire()方法在FairSync、NonFairSync中均有实现,尝试获取锁资源,核心代码如下:
1 // 公平锁 FairSync#tryAcquire() 方法 2 protected final boolean tryAcquire(int acquires) { 3 // 获取当前线程 4 final Thread current = Thread.currentThread(); 5 // 获取AQS的 state 6 int c = getState(); 7 // state == 0 当前没有线程占用锁资源 8 if (c == 0) { 9 // 判断是否有线程在排队,若有线程在排队,返回true 10 if (!hasQueuedPredecessors() && 11 // 尝试抢锁 12 compareAndSetState(0, acquires)) { 13 // 无线程排队,将线程属性设置为当前线程 14 setExclusiveOwnerThread(current); 15 return true; 16 } 17 } 18 // state != 0 有线程占用锁资源 19 // 占用锁资源的线程是否为当前线程 20 else if (current == getExclusiveOwnerThread()) { 21 // state + 1 22 int nextc = c + acquires; 23 // 锁重入超出最大限制 (int的最大值),抛异常 24 if (nextc < 0) 25 throw new Error("Maximum lock count exceeded"); 26 // 将 state + 1 设置给 state 27 setState(nextc); 28 // 当前线程拿到锁资源,返回true 29 return true; 30 } 31 return false; 32 } 33 34 // 非公平锁 NonFairSync#tryAcquire() 方法 35 protected final boolean tryAcquire(int acquires) { 36 return nonfairTryAcquire(acquires); 37 } 38 39 // 非公平锁 Sync#nonfairTryAcquire() 方法 40 final boolean nonfairTryAcquire(int acquires) { 41 // 获取当前线程 42 final Thread current = Thread.currentThread(); 43 // 获取AQS的 state 44 int c = getState(); 45 // 无线程占用锁资源 46 if (c == 0) { 47 // CAS 修改 state 的值,修改成功,设置线程属性为当前线程,返回占用锁资源标识 48 if (compareAndSetState(0, acquires)) { 49 setExclusiveOwnerThread(current); 50 return true; 51 } 52 } 53 // 有线程占用锁资源 54 // 占用锁资源的线程是当前线程(重入) 55 else if (current == getExclusiveOwnerThread()) { 56 // AQS 的 state + acquires 57 int nextc = c + acquires; 58 // 超出锁重入的上限(int的最大值),抛异常 59 if (nextc < 0) 60 throw new Error("Maximum lock count exceeded"); 61 // 将 state + acquires 设置到 state 属性 62 setState(nextc); 63 return true; 64 } 65 return false; 66 }
获取当前线程、AQS的state。AQS的state属性值为0,表示无线程占用锁资源,判断等待队列中是否有线程在排队,若有线程在排队,返回尝试抢锁失败标识,将线程添加进等待队列中。
2.2、addWaiter()
为当前线程创建入队节点AbstractQueuedSynchronizer$Node,入参mode表示锁类型,在AQS的静态内部类Node中有SHARE、EXCLUSIVE两个属性,SHARE代表共享锁、EXCLUSIVE代表排它锁。
AbstractQueuedSynchronizer#addWaiter() 核心代码:
1 // 等待队列的尾节点,懒加载,只能通过enq方法添加节点 2 private transient volatile Node tail; 3 4 private Node addWaiter(Node mode) { 5 // 当前线程、获取的锁类型封装为Node对象 6 Node node = new Node(Thread.currentThread(), mode); 7 // 获取等待队列的尾节点 8 Node pred = tail; 9 // 尾节点不为null 10 if (pred != null) { 11 // 将当前节点设置为等待队列的尾节点 12 node.prev = pred; 13 if (compareAndSetTail(pred, node)) { 14 pred.next = node; 15 return node; 16 } 17 } 18 // 等待队列为空,初始化等待队列节点信息 19 enq(node); 20 // 返回当前线程节点 21 return node; 22 }
等待队列不为空,将当前线程封装的Node节点添加进队列尾部;若等待队列为空,先初始化等待队列,然后在将Node节点添加进队列尾部。
2.2.1、enq()
等待队列尾节点为空时,执行enq()方法初始化等待队列,并将Node节点添加进等待队列中。
1 private Node enq(final Node node) { 2 for (;;) { 3 // 获取等待队列的尾节点 4 Node t = tail; 5 // 等待队列为空,初始化等待队列 6 if (t == null) { 7 // 初始化等待队列头尾节点 8 if (compareAndSetHead(new Node())) 9 tail = head; 10 } else { 11 // 当前线程的Node添加到等待队列中 12 node.prev = t; 13 if (compareAndSetTail(t, node)) { 14 t.next = node; 15 return t; 16 } 17 } 18 } 19 }
2.3、acquireQueued()
当前线程是否挂起,AbstractQueuedSynchronizer#acquireQueued() 核心代码:
1 final boolean acquireQueued(final Node node, int arg) { 2 // 获取锁资源标识 3 boolean failed = true; 4 try { 5 boolean interrupted = false; 6 // 自旋 7 for (;;) { 8 // 获取当前节点的前驱节点 9 final Node p = node.predecessor(); 10 // 当前节点的前驱节点为头节点,并获取锁资源成功 11 if (p == head && tryAcquire(arg)) { 12 // 将当前节点设置到head - 头节点 13 setHead(node); 14 // 原头节点的下一节点指向设置为null,GC回收 15 p.next = null; 16 // 设置获取锁资源成功 17 failed = false; 18 // 不管线程GC 19 return interrupted; 20 } 21 // 如果当前节点不是head的下一节点,获取锁资源失败,尝试将线程挂起 22 if (shouldParkAfterFailedAcquire(p, node) && 23 // 线程挂起, UNSAFE.park() 24 parkAndCheckInterrupt()) 25 interrupted = true; 26 } 27 } finally { 28 if (failed) 29 cancelAcquire(node); 30 } 31 }
查看当前排队的Node是否是head的next, 如果是,尝试获取锁资源, 如果不是或者获取锁资源失败那么就尝试将当前Node的线程挂起(unsafe.park())。
shouldParkAfterFailedAcquire检查并更新未成功获取锁资源的状态,返回true表示线程被挂起。
AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire() 核心代码:
1 static final class Node { 2 // 线程被取消 3 static final int CANCELLED = 1; 4 // 等待队列中存在待被唤醒的挂起线程 5 static final int SIGNAL = -1; 6 // 当前线程在Condition队列中,未在AQS对列中 7 static final int CONDITION = -2; 8 // 解决JDK1.5的BUG。共享锁在释放资源后,若头节点为0,无法确定真的没有后继节点 9 // 如果头节点为0,需要将头节点的状态改为 -3 ,当最新拿到锁资源的线程查看 10 // 是否有后继节点并且为当前锁为共享锁,需唤醒排队的线程。 11 static final int PROPAGATE = -3; 12 } 13 14 // 获取锁资源失败,挂起线程 15 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 16 // 获取当前节点的上一个节点的状态 17 int ws = pred.waitStatus; 18 // 上一节点被挂起 19 if (ws == Node.SIGNAL) 20 // 返回true,挂起当前线程 21 return true; 22 if (ws > 0) { 23 // 上一节点被取消,获取最近的线程挂起节点, 24 // 并将当前节点的上一节点指向最近的线程挂起节点 25 do { 26 node.prev = pred = pred.prev; 27 } while (pred.waitStatus > 0); 28 // 最近线程挂起节点的下一节点指向当前节点 29 pred.next = node; 30 } else { 31 // 上一节点状态小于等于0,存在线程处于等待状态,但未被挂起的场景 32 // 通过CAS将处于等待的线程挂起,避免在挂起前节点获取到锁资源 33 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 34 } 35 // 返回true,不挂起当前线程 36 return false; 37 }
在挂起线程前,确认当前节点的上一个节点的状态。若为1,代表是取消的节点,不能挂起;若为-1,代表后续节点中有挂起的线程;若为-2 (线程在等待队列 - Condition队列中)、-3 (避免线程无法唤醒的一个状态),需要将状态改为-1之后,才能挂起当前线程。
3、释放锁
1、unlock()
释放锁,ReentrantLock#unlock() 核心代码:
// 释放锁 public void unlock() { sync.release(1); }
unlock方法实际调用的是AQS的release方法,AbstractQueuedSynchronizer#release() 核心代码:
1 // 等待队列的头节点,懒加载,通过setHead方法初始化 2 private transient volatile Node head; 3 4 // 释放锁 5 public final boolean release(int arg) { 6 // 当前线程释放锁资源的计数值 7 if (tryRelease(arg)) { 8 // 当前线程玩去释放锁资源,获取等待队列头节点 9 Node h = head; 10 if (h != null && h.waitStatus != 0) 11 // 唤醒等待队列中待唤醒的节点 12 unparkSuccessor(h); 13 // 完全释放锁资源 14 return true; 15 } 16 // 当前线程未完全释放锁资源 17 return false; 18 }
1.1、tryRelease()
释放锁,Reenttrant$Sync#tryRelease()的核心代码
1 // 释放锁 2 protected final boolean tryRelease(int releases) { 3 // 修改 AQS 的 state 4 int c = getState() - releases; 5 // 当前线程不是持有锁的线程,抛出异常 6 if (Thread.currentThread() != getExclusiveOwnerThread()) 7 throw new IllegalMonitorStateException(); 8 // 是否成功的将锁资源完全释放标识 (state == 0) 9 boolean free = false; 10 // 锁资源完全释放 11 if (c == 0) { 12 // 修改标识 13 free = true; 14 // 将占用锁资源的属性设置为null 15 setExclusiveOwnerThread(null); 16 } 17 // state赋值 18 setState(c); 19 // 返回true表示当前线程完全释放锁资源; 20 // 返回false标识当前线程是由锁资源,持有计数值减少 21 return free; 22 }