【JUC】ReenTrantLock公平锁,非公平锁源码分析
其中有一些值的概念不太清楚,参考了:
https://blog.csdn.net/lsgqjh/article/details/63685058(这一位大佬,讲的很细!!)
https://blog.csdn.net/mulinsen77/article/details/84583716
在此感谢!
Lock接口功能:
public interface Lock {
// 获得锁
void lock();
// 获得锁
void unlock();
// lock非阻塞版本,成功返回true
boolean tryLock();
// 添加尝试时间,时间到返回false
boolean tryLock(long time, TimeUnit unit)
// 返回一个监视器对象
Condition newCondition();
}
AQS
在看ReenTrantLock之前,先大致看看AQS都做了什么:
这是一个简化的线程队列模型:
重要属性:
-
state:代表了资源是否处于锁定状态;
1:锁定(已经有线程拿锁,如果重入了,此值一直累加)2:未锁定
线程拿锁,就是通过CAS修改state,修改成功,则拿到锁;
-
Node内部类:
每一个Node装载一个线程;对线程通过双向链表的方式排队;
-
Node内部类:还定义资源是 独占 / 还是共享
也就是每个线程都有一个mode,标识是独占,还是共享;Node EXCLUSIVE:代表独占;
Node SHARED:代表共享;
先看几个重要方法,后面会用到;
acquire
acquire
:顾名思义获取,获取锁的方法
tryAcquire
:就先当作是获得锁,返回true,没拿到锁,返回false;这个方法是ReenTrantLock的方法,后面会讲;
addWaiter
:如果没拿到锁,将当前要拿锁的线程加入线程队列
acquireQueued
:已经入队完成,会进一步判断,后面讲;主要是判断线程是否被挂起;
- true:挂起
- false:拿锁成功了
所以:
当:拿锁失败,并且线程被挂起,就会执行selfInterrupt();
,执行线程的中断;
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
addWaiter
这个方法就是:(CAS操作入队)
让当前线程包装成Node,
队列存在,直接入队
如果队列不存在,调用enq(node);
,初始化队列,再入队
(入队:将node设置为队列的tail尾部节点,并且与tail双向关联起来)
private Node addWaiter(Node mode) {
// 包装线程为Node,并且是独占的
Node node = new Node(Thread.currentThread(), mode);
// 拿到线程队列的尾节点
Node pred = tail;
// 如果pred存在,即队列非空
if (pred != null) {
node.prev = pred;
// CAS操作成功入队,将Node设置为tail
if (compareAndSetTail(pred, node)) {
// 因为是双向链表,要再链一次
pred.next = node;
return node;
}
}
// 队列为空,调用enq,初始化队列,并入队
enq(node);
return node;
}
acquireQueued
此方法:是线程没拿到锁,入队之后执行;
主要是:如果发现当前线程的前一个结点是队列的head,那么会再次尝试拿锁;
也就是说,此时线程队列,就俩线程,一个是head,持锁线程,一个是当前线程;
那么当前线程会不断尝试拿锁(for (;;)
)
最终返回的boolean型interrupted
:
- true:挂起
- false:拿锁成功了
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 拿到当前线程的前一任节点
final Node p = node.predecessor();
// 发现前任是head,再次尝试拿锁
if (p == head && tryAcquire(arg)) {
// 拿锁成功,node设置为head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 判断是否将当前线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ReenTrantLock
实现Lock接口
ReenTrantLock只有一个内部属性:就是Sync内部类
的锁抽象对象
// 这是一个父类,两个子类分别实现公平锁,非公平锁
private final Sync sync;
三个内部类:
- Sync(继承AQS):锁抽象;
- NonfairSync(继承Sync):非公平锁抽象;
- FairSync(继承Sync):公平锁抽象;
构造器
我们在创建ReenTrantLock对象,调用构造器时,就会创建不同的Sync(锁抽象的实现)
// 非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁源码(拿锁,排队,重入锁)
当我们调用了lock.lock();
公平锁下,sync已经是FairSync
的实例了;
调用sync.lock()
public void lock() {
sync.lock();
}
然后调用FairSync内部类下的lock方法:(建议点进源码,看下)
acquire(1)
:此方法是AQS下的方法;(去上面看!)在内部是调用了下面的tryAcquire方法;
这个参数1是干嘛的:就代表尝试获取锁;之前AQS的属性state,如果为0表示未锁定;
这个1就是要通过compareAndSetState(0, acquires)
CAS操作进行加锁的;
(这里也是通过内存地址stateOffset,拿到state的状态,CAS操作不再赘述)
尝试将state设置为1,即拿到锁;
重点:tryAcquire方法(实现了拿锁,排队,重入锁)
static final class FairSync extends Sync {
final void lock() {
acquire(1); // 调用AQS acquire方法,前面讲了
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 从AQS中拿到当前资源的state状态
int c = getState();
// 如果为0,则表示未锁定,可以尝试获取锁
if (c == 0) {
// hasQueuedPredecessors是看当前线程队列中是否有其他线程(非公平锁没有此判断)
// 如果有其他线程,当前线程不允许拿锁,而是去排队
// 如果没有线程,并且CAS操作将state置1,那么当前线程就拿到了锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 设置独占的资源持有者为当前线程,即拿锁,并返回true
setExclusiveOwnerThread(current);
return true;
}
}
// state非0,即资源已被锁定
// 判断当前的线程,是不是占用锁的线程
// 是,则累加state,也就是重入锁的实现
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
// 叠加state状态
setState(nextc);
return true;
}
return false;
}
}
非公平锁源码
同样是lock()方法,不再赘述,只不过这里的Sync
实例,是NonfairSync
的实例;
与公平锁的主要区别:线程是否排队
直接看NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 加锁方法
final void lock() {
// CAS尝试加锁
if (compareAndSetState(0, 1))
// 成功,设置资源独占者为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 底层依然调用下面的tryAcquire
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
nonfairTryAcquire
方法是其父类Sync
下的方法
类似于公平锁的tryAcquire
方法
区别是:不再进行hasQueuedPredecessors()
方法的判断,直接尝试获取锁
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)
throw new Error("Maximum lock count exceeded");
// 叠加state状态
setState(nextc);
return true;
}
return false;
}
释放锁
释放锁的过程:
并不是说直接将state置为0即可,因为可能发生了多次重入;
每调用一次tryRelease
,state减一;
protected final boolean tryRelease(int releases) {
// 当前state-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state如果为0,可以释放锁
if (c == 0) {
free = true;
// 独占线程设为null
setExclusiveOwnerThread(null);
}
// 不能释放锁,state设置为c,即:减一操作;
setState(c);
return free;
}