一、概述
ReentrantLock是一种基于AQS框架的应用实现,是JDK中的一种线程并发访问的同步
手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。而且它具有比
synchronized更多的特性,比如它支持手动加锁与解锁,支持加锁的公平性。
AQS定义了一套多线程访问共享资源的同步器框架,是一个依赖状态(state)的同步器
ReentrantLock内部定义Sync实现了AbstractQueuedSynchronizer,公平锁、非公平锁都实现Sync
公平锁 FairSync
非公平锁 NonfairSyn(默认创建)
获取锁的逻辑:
- 自旋
- 获取锁
- 没有获取到锁
- 加入阻塞队列(CLH队列可认为双向链表)
- 设置前驱节点信号量-1(代表可被唤醒的)
- 阻塞当前线程
aqs 中node节点分为独占、共享,node节点
整体思维导图:
二、源码剖析
lock()获取锁
final void lock() {
this.acquire(1);
}
//AbstractQueuedSynchronizer进入acquire()
public final void acquire(int var1) {
if (!this.tryAcquire(var1) && this.acquireQueued(this.addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), var1)) {
selfInterrupt();
}
}
1、tryAcquire(var1)获取锁
protected final boolean tryAcquire(int var1) {
Thread var2 = Thread.currentThread();
int var3 = this.getState();
if (var3 == 0) {
//判断当前线程是否在阻塞队列中、不在设置节点thread为当前线程、返回true获取到锁
if (!this.hasQueuedPredecessors() && this.compareAndSetState(0, var1)) {
this.setExclusiveOwnerThread(var2);
return true;
}
//判断当前节点属性thread是否是当前线程,是设置state自增(说明reentrantlock可重入)
} else if (var2 == this.getExclusiveOwnerThread()) {
int var4 = var3 + var1;
if (var4 < 0) {
throw new Error("Maximum lock count exceeded");
}
this.setState(var4);
return true;
}
return false;
}
3、addWaiter(node)没有获取到锁加入队列、所有node节点都是独占模式
private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node var1) {
AbstractQueuedSynchronizer.Node var2 = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), var1);
AbstractQueuedSynchronizer.Node var3 = this.tail;
//如果尾节点不为空追加到尾部、考虑到多线程并发有可能加不上进入到enq
if (var3 != null) {
var2.prev = var3;
//采用cas算法保证原子性
if (this.compareAndSetTail(var3, var2)) {
var3.next = var2;
return var2;
}
}
this.enq(var2);
return var2;
}
private AbstractQueuedSynchronizer.Node enq(AbstractQueuedSynchronizer.Node var1) {
//采用自旋、最终保证多线程情况下节点一定可以追加到尾部
while(true) {
AbstractQueuedSynchronizer.Node var2 = this.tail;
if (var2 == null) {
if (this.compareAndSetHead(new AbstractQueuedSynchronizer.Node())) {
this.tail = this.head;
}
} else {
//设置当前节点前驱、注意此刻任意节点前驱一定有值,考虑到时间片的切换、next有可能没有设置好,这就是unparkSuccessor()为什么采用尾部遍历唤醒线程
var1.prev = var2;
if (this.compareAndSetTail(var2, var1)) {
var2.next = var1;
return var2;
}
}
}
}
4、acquireQueued()设置信号量、阻塞线程
inal boolean acquireQueued(AbstractQueuedSynchronizer.Node var1, int var2) {
boolean var3 = true;
try {
boolean var4 = false;
//采用自旋
while(true) {
AbstractQueuedSynchronizer.Node var5 = var1.predecessor();
//获取锁
if (var5 == this.head && this.tryAcquire(var2)) {
this.setHead(var1);
var5.next = null;
var3 = false;
boolean var6 = var4;
return var6;
}
//设置前驱节点信号量-1 阻塞当前线程
if (shouldParkAfterFailedAcquire(var5, var1) && this.parkAndCheckInterrupt()) {
var4 = true;
}
}
} finally {
if (var3) {
this.cancelAcquire(var1);
}
}
}
5、释放锁
public void unlock() {
this.sync.release(1);
}
public final boolean release(int var1) {
//释放锁 就是把state设置state-var1,把当前节点thread设置null便于gc
if (this.tryRelease(var1)) {
AbstractQueuedSynchronizer.Node var2 = this.head;
if (var2 != null && var2.waitStatus != 0) {
//唤醒后继节点
this.unparkSuccessor(var2);
}
return true;
} else {
return false;
}
}
private void unparkSuccessor(AbstractQueuedSynchronizer.Node var1) {
int var2 = var1.waitStatus;
if (var2 < 0) {
compareAndSetWaitStatus(var1, var2, 0);
}
AbstractQueuedSynchronizer.Node var3 = var1.next;
if (var3 == null || var3.waitStatus > 0) {
var3 = null;
//注意这部分,采用尾部遍历法找到离head节点最近且waitStatus<=0的节点并唤醒,信号量为1代表异常中断引起不会在执行后续逻辑
//为什么采用尾部遍历法、这就和上面enq()方法相关,节点入队时可能多线程切换导致next并不是连续的,而前驱节点都是连续的
for(AbstractQueuedSynchronizer.Node var4 = this.tail; var4 != null && var4 != var1; var4 = var4.prev) {
if (var4.waitStatus <= 0) {
var3 = var4;
}
}
}
if (var3 != null) {
LockSupport.unpark(var3.thread);
}
}