ReentrantLock.lock() 核心步骤:
1. 尝试加锁
2. 加锁失败将,失败的线程加入等待队列
3. 让当前线程阻塞
ReentrantLock.unLock() 核心步骤:
1. 从队列中取出第一个有效的线程
2. 唤醒该线程,lock中的步骤3
相对于Synchronized,RentrantLock有一下特性:
1. 支持公平和非公平;
2. 支持中断lock1.lockInterruptibly();
3.需要手动加锁解锁;
ReentrantLock的核心思想:
第一. 自旋 while(true),没拿到锁就不断的尝试
第二. 加锁,保证只有一个线程可以拿到锁 CAS 加锁 compare and swap
第三. 双向链表, 用双向链表双向链表队列数据结构实现公平或非公平锁
第四. LockSupport 使用park()、unpark()实现阻塞
伪代码:
//加锁
//第一 自旋
while(true){
//第二 CAS 加锁
if (unsafe.compareAndSwapInt(,,,)){ //加锁成功
break; //加锁成功的跳出循环
}
//将block的线程加入队列,以实现公平非公平锁
//第三 queue 记录需要加锁的线程
HashSet.linkedQueue.add(thread);
//第四 利用park block 线程
LockSupport.park();//加锁失败的在此等待
}
//解锁
LockSupport.unpark(thread);
ReentrantLock的源码分析,公平锁部分:
reentrantLock.lock();
加锁入口:
进入类ReentrantLock的lock()方法:
进入FairSync实现类的lock方法中:
进入其Sync父类AbstractQueuedSynchronizer的acquire()方法:
第一步: 尝试获取锁
分析tryAcquire(arg), 下面的步骤7,实现了可重入锁的功能。
第二步:将当前线程加到等待队列中。
分析方法addWaiter()
分析方法enq()
第三步: 阻塞当前线程
分析方acquireQueued()
注意,这里的park会阻塞线程,所以当有unpark该线程时候,会从park的地方继续执行下去。
1. 分析方法shouldParkAfterFarledAcquire()
解析方法parkAndCheckInterrupt()。
调用LockSupport.park将当前线程阻塞住,等待白唤醒。
解锁入口:
解析方法release()
解析方法tryRelease(arg)
解析方法unparkSuccessor()
非公平锁部分如何实现:
如下图,对于非公平锁多了 一步直接去加锁的过程, 不管你等待队列里面有多少等待的,这里上来下试图去拿一下锁,获取锁不成功的情况下再尝试走下面的逻辑:
每一个Node都有下面5个总有的属性:
prev - 上一个Node
next - 下一个Note
thread - 保存线程,以便后面唤醒
SHARED/EXCLUSIVE - 独占、共享属性
waitStatus - 节点的生命状态(信号量),包含下面值
SINGAL = -1 (可被唤醒)
CANCELLED = 1 (出现异常了,例如中断引起的)
CONDITION = -2 (条件等待)
PROPAGATE = -3 (传播)
初始状态 = 0 (Node刚刚被创建出来默认0)
队列的样子大概长这样:
类的注意结构关系:
主类: ReentrantLock
ReentrantLock 中有个内部类: Sync, FairSync, NonfairSync
Sync 是 FairSync, NonfairSync的父类,即FairSync, NonfairSync是Sync的2个不同实现
Sync 继承自AbstractQueuedSynchronizer
AbstractQueuedSynchronizer中主要有Node相关实现,构建队列的数据结构
AbstractQueuedSynchronizer 又继承自AbstractOwnableSynchronizer
AbstractOwnableSynchronizer记录了那个线程拥有当前独占锁