ReentrantLock(Lock)锁的底层实现与源码解析
目录
辅助阅读超链接:AQS抽象类源码解析: AQS抽象类源码解析
1.内置静态内部类及其对象:
经过观察ReentrantLock把所有Lock接口的操作都委派到一个Sync类上,该类继承了AbstractQueuedSynchronizer(简称AQS),由于有公平锁和非公平锁需要两个Sync对象,通过继承模板化代码,下面走进这个两个静态内部类:
1.1代表公平锁的静态内部类
private final Sync sync;
//代表公平锁的静态内部类
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();//用于非公平锁继承重写
//两个sync对象同时需要该方法,故写在父类
//nonfairTryAcquire方法将是lock方法间接调用的第一个方法,每次请求锁时都会首先调用该方法。
//第一次if判断有无锁//第二次判断是否是自己占有锁(偏向)//否则返回false
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;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
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;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//判断是否是当前占有锁的线程在读
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
//新建AQS的条件对象
final ConditionObject newCondition() {
return new ConditionObject();
}
//获取该临界区被哪个线程占有
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//获取内部计数器数值
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//通过内部计数器数值判断是否有锁
final boolean isLocked() {
return getState() != 0;
}
//从流重新构造实例(即对其进行反序列化)
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // 初始重置
}
}
1.2代表非公平锁的静态内部类
//非公平需要重写两个方法
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
//通过CAS设置内部计数器为1,即上第一次锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
2.ReentrantLock的构造方法
两种,即公平、非公平
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
3.lock加锁过程
Reentrant.lock()方法的调用过程(默认非公平锁):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-od8VJwu0-1633697176490)(D:\javaInstall\Typora\My_All_Pictures\image-20211008191929661.png)]
这些Template模式导致很难直观的看到整个调用过程,其实通过上面调用过程及AbstractQueuedSynchronizer的注释可以发现,AbstractQueuedSynchronizer中抽象了绝大多数Lock的功能,而只把tryAcquire方法延迟到子类中实现。tryAcquire方法的语义在于用具体子类判断请求线程是否可以获得锁,无论成功与否AbstractQueuedSynchronizer都将处理后面的流程。
详见:
Java锁–Lock实现原理(底层实现)_隔壁老王的专栏-CSDN博客_lock的底层实现
4.lock加锁阻塞
LockSupport.park(this)最终把线程交给系统(Linux)内核进行阻塞。当然也不是马上把请求不到锁的线程进行阻塞,还要检查该线程的状态,比如如果该线程处于Cancel状态则没有必要,具体的检查在shouldParkAfterFailedAcquire中。
总体看来,shouldParkAfterFailedAcquire就是靠前继节点判断当前线程是否应该被阻塞,如果前继节点处于CANCELLED状态,则顺便删除这些节点重新构造队列。
至此,锁住线程的逻辑已经完成。
5.释放锁
找出第一个可以unpark的线程,一般说来head.next == head,Head就是第一个线程,但Head.next可能被取消或被置为null,因此比较稳妥的办法是从后往前找第一个可用线程。貌似回溯会导致性能降低,其实这个发生的几率很小,所以不会有性能影响。之后便是通知系统内核继续该线程,在Linux下是通过pthread_mutex_unlock完成。之后,被解锁的线程进入上面所说的重新竞争状态。
6.Lock 和 Synchronized区别
AbstractQueuedSynchronizer通过构造一个基于阻塞的CLH队列(Craig, Landin, and Hagersten (CLH))容纳所有的阻塞线程,而对该队列的操作均通过**Lock-Free(CAS)**操作,但对已经获得锁的线程而言,ReentrantLock实现了偏向锁的功能。
synchronized的底层也是一个基于CAS操作的等待队列,但JVM实现的更精细,把等待队列分为ContentionList( 处于wait状态的线程,会被加入到_WaitSet)和EntryList(处于等待锁block状态的线程,会被加入到该列表),目的是为了降低线程的出列速度;当然也实现了偏向锁,从数据结构来说二者设计没有本质区别。但synchronized还实现了自旋锁,并针对不同的系统和硬件体系进行了优化,而Lock则完全依靠系统阻塞挂起等待线程。
当然Lock比synchronized更适合在应用层扩展,可以继承AbstractQueuedSynchronizer定义各种实现,比如实现读写锁(ReadWriteLock),公平或不公平锁;同时,Lock对应的Condition也比wait/notify要方便的多、灵活的多。