1.8ReentrantLock史上最详细源码分析
2019-11-02 本文已影响0人
IT界刘德华
前言
ReentrantLock是基于AQS实现的可重入的独占锁,当然它可以实现公平锁和非公平锁,下面会一一进行讲解。这里一些AQS的知识就不过多的介绍,详细见我的AQS篇章https://www.jianshu.com/p/54d372425e54
类结构
public class ReentrantLock implements Lock, java.io.Serializable {
//内部类持有对象,是公平锁还是非公平锁,该对象继承了AbstractQueuedSynchronizer
private final Sync sync;
//默认初始化是非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//传参true知道公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//继承AQS框架
abstract static class Sync extends AbstractQueuedSynchronizer {
}
//非公平锁
static final class NonfairSync extends Sync {}
//公平锁
static final class FairSync extends Sync {}
}
源码分析
NonfairSync(非公平锁分析)
- lock加锁,如果获得锁将重入状态和数量改下,并设置独占线程为当前线程,否则继续获取资源
final void lock() {
//状态是0代表可以获取锁,如果内存中是0就获取锁,并cas改成1表示得到锁重入次数是1
if (compareAndSetState(0, 1))
//得到锁则将当前线程往爷爷类AbstractOwnableSynchronizer设置为拥有独占锁的线程
setExclusiveOwnerThread(Thread.currentThread());
//没获得锁
else
//请求获得资源
acquire(1);
}
- compareAndSetState(cas比较状态更新状态值,达到加锁的目的)
protected final boolean compareAndSetState(int expect, int update) {
// AbstractQueuedSynchronizer对象+状态偏移量找到内存的状态值
//如果内存的状态值和expect期望值相等,则用update更新内存的状态值
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
- acquire就是AQS框架讲到的了,这里不做太多的解释,详见AQS篇https://www.jianshu.com/p/54d372425e54
,这里只会对tryAcquire重写进行讲解
//获取资源,如果tryAcquire没获取到资源,则acquireQueued从等待队列自旋去获取资源
//如果依然没获取到则进入阻塞状态,当然如果acquireQueued在判断取到的线程已经被中断
//则调用selfInterrupt去打断线程
//参数arg,该值会传送到{@link #tryAcquire},但否则不会被解释,可以代表任何内容,如reentrantLock传的是1。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire(NonfairSync非公平锁对其进行重写),尝试获取资源
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
- nonfairTryAcquire非公平锁获取,如果失败就返回false,如果锁还未被获取则设置独占线程,改重入次数,返回获锁成功,若果锁已经被获取,则累计重入次数设置为新的重入次数,返回获锁成功
//在sync内部的方法
//非公平锁获取资源
final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取父类AbstractQueuedSynchronizer的计数值
int c = getState();
//锁还未被获取
if (c == 0) {
//acquires=1传进来的,cas设置state的状态,改为已经被当前线程获得锁,并标记重入次数
if (compareAndSetState(0, acquires)) {
//设置当前线程为当前锁的独占线程
setExclusiveOwnerThread(current);
//获得锁成功
return true;
}
}
//状态!=0s说明已经被获得锁
//如果获得锁的独占线程是当前线程则可以继续获得锁
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;
}
FairSync(公平锁分析)
- lock加锁,如果获得锁将重入状态和数量改下,并设置独占线程为当前线程,否则继续获取资源。公平锁的lock少了设置重入次数和独占线程设置,而是自己调acquire直接获取资源
final void lock() {
acquire(1);
}
- acquire就是AQS框架讲到的了,这里不做太多的解释,详见AQS篇,这里只会对tryAcquire重新进行讲解
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- tryAcquire(尝试获取公平锁)
//尝试获取公平锁
protected final boolean tryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//锁的重入次数
int c = getState();
//锁还未被获取
if (c == 0) {
//hasQueuedPredecessors判断队列是否还有等待时间长的线程,也就是排在队列前的
//如果没有则比较设置重入值,获锁成功设置当前线程为锁的独占线程返回true
if (!hasQueuedPredecessors() &&
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;
}
- hasQueuedPredecessors(查询是否有任何线程等待获取的时间比当前线程长)
判断等待队列里不止一个节点,队列里的下一个指针是空,则返回true,代表已经有等待节点
判断等待队列里不止一个节点,队列里的下一个指针不是空,则要判断头结点的下一个节点是不是当前线程,如果是则返回false,如果不是则返回true
//查询是否有任何线程等待获取的时间比当前线程长
//如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false。
//如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,
//有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,
//此时tail=null,head=newNode,head.next=null。
//如果h!=t成立,head.next != null,则判断head.next是否是当前线程,如果是返回false,
//否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),
//未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
tryLock(其实调用的就是Sync类的nonfairTryAcquire非公平获取资源方法)
没获取到锁就返回了不会跟lock一样还要一直阻塞
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
unlock(解锁)
调用的是AQS类的release方法
public void unlock() {
sync.release(1);
}
- AQS类的release方法,该方法的tryRelease是在ReentrantLock内部类Sync重新的
一般在老二获得资源后就会将自己设置为head,所以在释放资源的时候用head开始释放资源,并唤醒老二(后继节点)
//释放资源
public final boolean release(int arg) {
//将重入次数减到0后释放锁成功
if (tryRelease(arg)) {
//头结点
Node h = head;
//头结点不是空并且它的状态不是初始化阶段,如果是<0的状态会将当前节点置为0
//并且找到有效的后继节点进行释放
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
//释放成功
return true;
}
//释放失败
return false;
}
- tryRelease
试释放锁,releases释放次数,因为锁是可重入的,上了几次锁就要解锁几次
//尝试释放锁,releases释放次数,因为锁是可重入的,上了几次锁就要解锁几次
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;
}
有些方法没去讲,因为是调用AQS的方法,已经说过就不讲了
AQS详见我的AQS源码解析篇https://www.jianshu.com/p/54d372425e54
觉得还可以的话给我点点赞呀!!!!!