一、Lock原理
以ReentrantLock为例,其是基于AQS实现的可重入锁。
- ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,Sync类继承了AbstractQueuedSynchronizer。
- Sync有两个子类:NonfairSync(非公平锁、提高了吞吐量)、FairSync(公平锁)。
- 公平锁:利用阻塞队列FIFO的特性、先到先得
- 非公平锁:线程节点加到阻塞队列之前,不管是否有线程排队,先tryAcquire()抢一下锁试试。
- 默认选择非公平锁,因为通过大量的测试下来,发现非公平锁的性能优于公平锁。
获取锁流程和释放锁流程建议结合下面的源码分析一起理解;此处是做的一个总述。
1、lock()获取锁流程
首先,直接调用AQS实现类Sync的lock()方法,由于ReentrantLock有公平锁和非公平锁之分,所以这里sync.lock()有两种实现:
非公平锁:NonfairSync#lock()
- 首先通过CAS操作尝试将state从0变成1,如果成功,则将当前线程设置为锁的持有者,快速返回;
- 只有当存在锁重入、锁竞争的时候,才会进入到acquire()方法,尝试去获取锁。而acquire()方法是AQS提供的模板方法,ReentrantLock中只需实现tryAcquire()方法,自定义获取锁的逻辑即可;即:NonFairSync#tryAcquire()方法用来判断锁是否可以获取成功,
- 如果
state==0
表示没有线程正在竞争该锁,通过CAS将state状态值设置为1,并将当前线程设置为锁的持有者。 - 如果
state!=0
说明已经有线程已经拥有了该锁;
如果是当前线程就是锁的持有者,则直接state++;
否者,如果是别的线程持有锁,则tryAcquire()
方法返回false;
tryAcquire()
方法返回false表示获取锁失败,进入到AQS的线程阻塞入队逻辑,其中包括:
调用
addWriter()
方法就会把线程包装为一个Node节点添加到等待队列队尾。
接下来**acquireQueued()方法将已经追加到队列的线程节点(即addWriter()的返回值)进行阻塞;
- 阻塞前会调用
tryAccquire()
尝试是否能再次获得锁。
公平锁:FairSync#lock()
和非公平锁逻辑基本一致,有两个不同点;
- 直接进入AQS的
acquire()
方法尝试获取锁,不会先尝试CAS将state从0改为1。 - 在FairSync#tryAcquire()方法中,及时某个时间点没有线程锁,也会通过hasQueuedPredecessors()方法去判断CLH队列中有没有线程在排队?
- 如果有,并且当前线程没有持有锁,直接进入到AQS的CLH队列。
- 否则和非公平锁一个逻辑。
2、unlock()释放锁流程:
直接进入到AQS的release()
方法,不论是公平锁还是非公平锁,针对release()
模板方法,Sync只提供一个tryRelease()
方法的实现,用于判断锁是否可以成功释放:
- 如果释放锁的线程不是锁的持有者,直接抛出异常IllegalMonitorStateException;
- 否者,修改状态值state,做state–操作,当state=0时释放锁、清空锁的持有者信息。
- 此外,由于释放锁不存在竞争,所有没有采用CAS修改state。
3、面试题2:为什么非公平锁的性能优于公平锁?
- 因为公平锁需要两个时间:阻塞队列中排队、线程唤醒;所以经过线程的上下文切换之后 再加到 运行队列中有一个延时的过程:
- 阻塞队列中的线程状态要先转换为RUNNING就绪状态、放到运行队列去;
- 然后再根据优先级、调度算法调度执行起来。
- 也就是说公平锁有线程上下文切换时间 + 线程调度的延迟时间
- 而非公平锁,直接上来就去争抢线程调度的资源。
二、ReentrantLock源码分析
1、变量和构造器
NonfairSync和FairSync有很多共用逻辑,所以抽象出一个Sync类。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// 默认是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// true 表示公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
公平锁?
阻塞队列FIFO、先到先得
非公平锁?
线程节点加到阻塞队列之前,不管是否有线程排队,先tryAcquire()抢一下锁试试。
默认是非公平锁
2、lock()加锁
通过组合的Sync类实现加锁功能;
public void lock() {
// 直接通过sync的lock方法上锁
sync.lock();
}
而Sync包含两个子类NonfairSync、FairSync,分别表示非公平锁和公平锁。
1)非公平锁的加锁
static final class NonfairSync extends Sync {
// 由ReentrantLock调用获取锁
final void lock() {
// 非公平锁,直接抢锁,不管有没有线程排队
if (compareAndSetState(0, 1))
// 上锁成功,那么标识当前线程为获取锁的线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 抢锁失败,进入AQS的标准抢锁逻辑
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 使用父类提供的获取非公平锁
return nonfairTryAcquire(acquires);
}
}
Sync中非公平的获取锁
abstract static class Sync extends AbstractQueuedSynchronizer {
// 非公平锁获取锁的方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 当执行到这里时,说明某个持有锁的线程释放了锁,那么可以尝试抢锁。
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 当前线程就是持有锁的线程,表明锁重入。
else if (current == getExclusiveOwnerThread()) {
// 锁重入次数
int nextc = c + acquires;
// 超过了int的表示范围,表明符号溢出,抛出异常。
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 返回false,表示需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作等待其他线程唤醒
return false;
}
}
2)公平锁的加锁
static final class FairSync extends Sync {
final void lock() {
// 没有尝试抢锁,直接进入AQS标准获取锁流程
acquire(1);
}
// AQS调用,子类自己实现获取锁的逻辑
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 当执行到这里时,说明某个持有锁的线程释放了锁 或 就没有线程获取锁,那么可以尝试抢锁。
if (c == 0) {
// 这里和非公平锁的区别在于:hasQueuedPredecessors看看队列中是否有线程正在排队
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;
}
// 返回false,表示需要AQS来将当前线程放入阻塞队列,然后进行阻塞操作等待其他线程唤醒
return false;
}
}
3、unlock()释放锁
主要体现在AQS.release()方法和Sync.tryRelease()方法中,修改状态值让它自减,state=0时释放锁(此处由于没有竞争出现,不会使用CAS),调整等待链表。
释放锁的时候不区分是公平锁还是非公平锁,并且整个实现过程中,Lock大量使用CAS+自旋。
1> 释放锁:
public void unlock() {
sync.release(1);
}
由于Sync是AQS的子类,进入到AQS的release()方法:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
Sync类中tryRelease()对AQS的模板方法实现:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 释放锁操作
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 如果当前线程不是上锁的那个线程
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 不是重入锁,当前线程已经释放了锁。我们把AQS用于记录持有锁对象线程的变量exclusiveOwnerThread设置为null,表明释放锁成功。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 在setState之前,state全局变量并没有改变;也就意味着在setState之前,没有别的线程能获取锁,保证了上面2行操作的原子性。
setState(c);
// 告诉AQS,当前锁释放成功,可以去唤醒正在等待获取锁的线程了。
return free;
}
}