在调用ReentrantLock中的lock()方法,内部会去调用 sync.lock(),具体实现分别在sync的子类 NoFairSync、FairLockSync中的 lock() 方法
public void lock() {
sync.lock();
}
//公平锁实现
final void lock() {
acquire(1);
}
//非公平锁实现
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
从代码中可以看出,
公平锁和非公平锁在线程获取的时候第一个区别就是,公平锁直接调用 acquire(1) 获取锁,而非公平锁会先使用 CAS 尝试获取锁,如果获取到就将当前锁设置为独占锁,如果获取不到才调用 acquire(1) 获取锁
接着分析 acquire(1)
方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire中有四个方法,逐个进行分析:
先看第一个方法 tryAcquire(arg)
,这个方法在公平锁和非公平锁都进行重写
公平锁:
protected final boolean tryAcquire(int acquires) {
//此时 acquires=1
//先获取当前线程
final Thread current = Thread.currentThread();
//获取锁状态
int c = getState();
//如果锁状态=0,表示当前锁没有被其他线程占有
if (c == 0) {
//hasQueuedPredecessors()判断双向链表中是否有线程在排队,没有线程排队(返回false),强锁
//如果有线程排队,瞜一眼当前线程是否排在第一个,如果是(返回false),抢锁
if (!hasQueuedPredecessors() &&
//使用CAS尝试获取锁,获取到了,将state设置为1
compareAndSetState(0, acquires)) {
//设置当前线程为独占锁线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为0,表示锁已经被其他线程持有,判断持有锁的线程是否是当前线程,如果是
else if (current == getExclusiveOwnerThread()) {
//state加上acquires
int nextc = c + acquires;
//防止溢出,抛出异常
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//否则,设置state值
setState(nextc);
return true;
}
return false;
}
非公平锁:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁状态state
int c = getState();
//如果锁没有被线程持有
if (c == 0) {
//直接使用CAS获取锁
if (compareAndSetState(0, acquires)) {
//获取到锁,将当前线程设置为独占锁线程
setExclusiveOwnerThread(current);
//直接返回
return true;
}
}
//如果锁已经被线程持有,判断持有所线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
//线程状态加上acquires
int nextc = c + acquires;
//溢出,直接抛出异常
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//设置线程状态,代表可重入成功
setState(nextc);
//直接返回true
return true;
}
return false;
}
从公平锁和非公平锁的 tryAcquire() 代码中可以看出,
当锁状态为0时,公平锁会先去看等待锁队列中是否有其他线程在等待锁,如果没有再去获取锁,如果有再看当前线程是否排在第一个,如果是再获取锁,否则无法获取锁而非公平锁直接使用CAS获取锁
再看第二个方法 addWaiter(Node.EXCLUSIVE)
private Node addWaiter(Node mode) {
//将当前线程封装为一个双向链表节点
Node node = new Node(Thread.currentThread(), mode);
//获取链表尾节点
Node pred = tail;
// tail还未初始化时不能走这个逻辑,由于刚开始head和tail都指向null,执行enq()方法
//如果尾节点不为空,将当前节点放在尾部
if (pred != null) {
//将尾结点设置为当前节点的前驱节点
node.prev = pred;
//使用CAS将尾结点设置为当前节点
if (compareAndSetTail(pred, node)) {
//如果设置成功,尾结点的后继节点连接到当前节点
pred.next = node;
//返回当前节点
return node;
}
}
//节点没有初始化,执行enq()
enq(node);
return node;
}
private Node enq(final Node node) {
//死循环
for (;;) {
//获取尾结点,用t指向
Node t = tail;
//如果尾结点为空
if (t == null) {
//使用CAS初始化虚拟的头结点
if (compareAndSetHead(new Node()))
//将tail也指向head
tail = head;
} else {
// 节点添加
// 尾结点不为空,将当前节点的前驱结点指向尾结点
node.prev = t;
//使用CAS将当前节点设置为尾结点,此处使用临时变量t意义就是将t作为期望值,保证操作的原子性
if (compareAndSetTail(t, node)) {
//尾结点的后继指向当前节点
t.next = node;
//返回插入节点之前的尾结点,目的就是为了结束for循环
return t;
}
}
}
}
从addWaiter()方法可以看出,
当 tryAcquire() 方法没有获取到锁,执行 addWaiter() 就是将当前线程封装为一个Node节点放入到一个双向链表中,如果放入之前链表未进行初始化,先进行初始化,再添加到链表中,并返回当前节点,如果链表已经初始化且链表不为空,就将当前节点放入到链表的尾部,返回当前节点
再看第三个方法 acquireQueued(final Node node, int arg),再次获取锁,如果获取锁失败,就会一直自旋,尝试获取锁,直到成功获取锁
final boolean acquireQueued(final Node node, int arg) {
//定义一个失败的标志,true失败
boolean failed = true;
try {
//定义一个中断的标志,false未中断
boolean interrupted = false;
//死循环
for (;;) {
//获取node节点的前驱结点
final Node p = node.predecessor();
//如果前驱结点为头结点,说明node是第一个节点,那么当前线程就去竞争锁使用tryAcquire,逻辑与前面一致
if (p == head && tryAcquire(arg)) {
//获取到了锁,将node的节点赋值给头结点,并将node的前驱后继置空
setHead(node);
p.next = null; // help GC
//将失败标志设为false,证明成功获取到锁
failed = false;
//返回false
return interrupted;
}
//如果当前线程不在链表头部或者获取锁失败
//shouldParkAfterFailedAcquire 用于判断在获取锁失败后,当前线程是否应该进入等待状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//线程中断
interrupted = true;
}
} finally {
if (failed)
//获取锁成功后,将该节点从等待队列中移除,从而取消线程获取锁的尝试
cancelAcquire(node);
}
}
final Node predecessor() throws NullPointerException {
//获取当前节点的前驱节点
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//该方法主要用于在获取锁失败时进行适当的线程挂起和恢复。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱节点的状态
int ws = pred.waitStatus;
//如果节点状态为 -1 ,表示该节点需要被前驱结点进行唤醒
//表示前驱节点已经设置为唤醒当前节点的状态。这意味着当前线程应该进入等待状态(park)
if (ws == Node.SIGNAL)
return true;
//节点状态为 1,表示节点已取消排队
//表示前驱节点已经取消排队,或者出现了一些异常情况。在这种情况下,需要遍历等待队列,找到一个合适的前驱节点,并将当前节点插入到合适的位置
if (ws > 0) {
do {
//将当前节点的前驱节点指向当前节点前驱节点的前驱节点
node.prev = pred = pred.prev;
//找到一个有效的前驱节点
} while (pred.waitStatus > 0);
//前驱节点的前驱节点后继节点指向当前节点
pred.next = node;
} else {
//状态为 0 或 -3 或 -2 ,使用CAS将前驱节点状态改为 -1,前驱节点需要唤醒当前节点
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
//shouldParkAfterFailedAcquire返回true,说明需要将当前线程挂起,执行parkAndCheckInterrupt
//将当前线程挂起(park)并检查是否被中断。
private final boolean parkAndCheckInterrupt() {
// 立即挂起线程,导致当前线程进入等待状态,直到被其他线程唤醒或中断。
LockSupport.park(this);
// 检查线程的中断状态,如果为true,清除中断状态将中断状态设置为false,返回true,否则返回false
return Thread.interrupted();
}
从acquireQueued(final Node node, int arg)方法可以看出,获取当前节点的前驱节点,如果前驱节点为头结点,就开始调用 tryAcquire() 方法获取锁,获取成功就直接返回,如果获取失败,判断是否需要挂起当前线程,如果前驱节点waitStatus为-1,通过LockSupport.park()挂起当前线程,如果前驱节点waitStatus>0(waitStatus=1),表示前驱结点处于取消状态,移除前驱节点,否则就将前驱节点waitStatus设置为 -1,不挂起当前线程
在调用 ReentrantLock的 unlock()方法时,内部会调用 sync.release(1) 方法
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//如果返回true,表示锁已经释放,false:还未释放
if (tryRelease(arg)) {
//获取头结点
Node h = head;
//头结点不为空并且等待状态不为0
if (h != null && h.waitStatus != 0)
//从后往前遍历唤醒等待节点
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//获取当前锁状态,将锁状态减去releases
int c = getState() - releases;
//如果持有锁的线程不是释放锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果锁状态为0,表示当前锁已被释放
if (c == 0) {
free = true;
//设置独占锁线程为null
setExclusiveOwnerThread(null);
}
//设置锁状态
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
//获取头结点等待状态
int ws = node.waitStatus;
//小于0
if (ws < 0)
//将头结点等待状态设置为0
compareAndSetWaitStatus(node, ws, 0);
//获取头结点下一个节点
Node s = node.next;
//下一个节点为null 或者 等待状态 >0
if (s == null || s.waitStatus > 0) {
//将后继节点设置为 null
s = null;
//从后往前遍历
for (Node t = tail; t != null && t != node; t = t.prev)
//如果遇到节点等待状态 <= 0
if (t.waitStatus <= 0)
//将t赋值给s
s = t;
}
// 如果s不为null
if (s != null)
//唤醒s
LockSupport.unpark(s.thread);
}