ReentrantLock的介绍
二、ReentrantLock的lock方法:
2.1简单分析
进入lock方法后,发现内部调用了sync.lock()方法,去找方法的实现,发现了两个实现。
Fairsync 公平锁,每个线程都会在执行lock方法时,查看是否有线程排队,如果有,直接去排队,如果没有才会去尝试竞争一个锁资源。
NonfairSync 非公平锁,每个线程都会在执行lock方法时,先尝试获取所资源,获取不到再排队。
非公平锁的效率更高,在new Reentrantlock时候传入参数true。
公平锁直接使用无参构造方法。
从源码的角度也发现了公平锁直接调用acquire方法尝试获取锁,而非公平锁会先基于CAS方式尝试获取锁资源,如果获取不到才会执行acquire方法。
2.2分析AQS
AQS就是AbstractQueuedSynchronizer类,AQS内部维护着一个队列,有三个核心属性state head tail
2.3lock()方法源码
1 //非公平锁的lock方法
2 final void lock() {
3 // 使用CAS方式,将state从0改为1
4 if (compareAndSetState(0, 1))
5 // 证明state修改成功,也就代表获取锁资源成功
6 // 将当前线程设置到AQS的exclusiveOwnerThread(AOS中),代表当前线程拿着锁资源
7 setExclusiveOwnerThread(Thread.currentThread());
8 else
9 acquire(1);
10 }
11 //公平锁的lock方法
12 final void lock() {
13 acquire(1); }
三、acquire()方法
//不管是公平锁还是非公平锁,都会调用acquire方法
public final void acquire(int arg) {
// tryAcquire方法分为两种实现,第一种是公平锁,第二种是非公平锁
// 公平锁操作:如果state为0,再看是否有线程排队,如果有就去排队.如果state不为0,看是否是自己拿着锁资源,是否可以做一个锁重入的操作,如果可以直接获取锁
// 非公平锁:如果state为0,直接尝试CAS修改.如果state不为0,如果是锁重入的操作,直接获取锁
if (!tryAcquire(arg) &&
// addWaiter在线程没有通过tryAcquire拿到锁资源时,需要将当前线程封装为Node对象,去AQS排队
// acquireQueued方法查看当前线程是否排在队伍前面的,如果是尝试获取锁资源,如果长时间没拿到锁,也需要将当前线程挂起
acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))
selfInterrupt();
}
四、tryAcquire()方法
是AQS提供的,内部并没有任何的实现,需要继承AQS的类自己去实现逻辑代码,查看到tryAcquire在ReentantLock中提供了两种实现:公平锁和非公平锁
非公平锁
//非公平锁的实现方式
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取AQS的state
int c = getState();
// 如果state为0,代表当前没有线程占用锁资源
if (c == 0) {
// 直接基于CAS的方式,尝试修改state,从0修改为1,如果成功就代表拿到锁资源
if (compareAndSetState(0, acquires)) {
// 将exclusiveOwnerThread属性设置为当前线程
setExclusiveOwnerThread(current);
// 返回true
return true;
}
}
// 说明state肯定不为0,不为0就代表当前lock被线程占用
// 判断占用锁资源的线程是不是当前线程
else if (current == getExclusiveOwnerThread()) {
// 锁重入操作,直接对state加1
int nextc = c + acquires;
// 判断锁重入是否达到最大值
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 设置AQS中的state
setState(nextc);
return true;
}
return false;
}
公平锁
//公平锁实现
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 没有线程占用锁资源,首先查看有没有线程排队
if (!hasQueuedPredecessors() &&
// 如果没有线程排队,尝试CAS获取锁资源
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;
}
return false;
}
}
公平锁和非公平锁的tryAcquire方法唯一区别就是,当判断state为0之后
- 公平锁会 先查看是否有线程在排队,如果有,直接返回false,如果没有线程排队,执行CAS尝试获取锁资源
- 非公平锁不管有没有线程排队,直接以CAS的方式尝试获取锁资源
五、addWaiter()方法
在线程执行tryAcquire方法没有获取锁资源之后,会返回false,再配置上if中的!操作,会执行&&后面的方法,而在
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))的参数中执行了addWaiter()方法,要将当前获取锁失败的线程封装为Node,排队到AQS的队列中。
整体逻辑为先初始化Node节点,将当前线程传入,并且标识为互斥锁。
尝试将当前Node插入到AQS队列的末尾,
- 队列为空,执行enq,先初始化空Node作为头,再将当前Node插入,作为tail
- 队列不为空,直接将当前Node插入,作为tail
//获取锁失败 排队到AQS的队列中
private Node addWaiter(Node mode) {
// 将线程封装为Node对象
Node node = new Node(Thread.currentThread(), mode);
// 获取到tail节点
Node pred = tail;
// 如果tail节点不为null,表示正在有人排队
if (pred != null) {
// 将当前节点的prev指向tail
node.prev = pred;
// 避免并发问题,以CAS方式将tail指向当前线程
if (compareAndSetTail(pred, node)) {
// 将之前的tail的next指向当前节点
pred.next = node;
// 返回当前节点
return node;
}
}
// 如果在队列为空或者CAS操作失败后,执行enq方法将当前线程排队队列末尾
enq(node);
return node;
}
六、ReentantLock的acquireQueued方法
首先查看当前node是否排在队列的第一个位置(不算head),直接再次执行tryAcquire方法竞争锁资源,尝试将当前线程挂起,最终排在有效节点后才会将当前线程挂起。
七、ReentantLock的unlock方法
unlock释放锁操作不分为公平和非公平,都是执行sync的release方法
释放锁的核心就是将state从大于0的数值改成0即为释放锁成功,unlock应该会涉及到AQS队列中阻塞的线程进行唤醒,阻塞的方法是park方法,唤醒必然是unpark方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理