ReentarantLock源码浅析
创始人
2024-02-17 21:10:31
0

ReentrantLock

ReentrantLock: 就是一个互斥锁,可以让多线程执行期间,只有一个线程在执行指定的一段代码
使用方式: 直接new

        ReentrantLock reentrantLock = new ReentrantLock();//加锁reentrantLock.lock();try{//业务代码......} finally {//释放锁reentrantLock.unlock();}

lock方法

public void lock() {sync.lock();
}

sync.lock();有两个实现

1 FairSync: 公平锁
每哥线程会在执行lock方法时,会先查看是否有线程排队,如果有,直接去排队,没有会去尝试竞争一下锁资源
2 NonfairSync: 非公平锁
每个线程会执行lock方法时,先尝试获取,获取不到再排队

如果要使用公平锁需要在构建时 new ReentarntLock(true);
要使用非公平锁直接使用无参构造.

从源码角度,公平锁直接调用 acquire 方法尝试获取锁,
而非公平锁会先基于 CAS的方式尝试获取锁资源,如果获取不到,再调用 acquire方法 尝试获取锁

分析 AQS

AQS 就是 AbstractQueuedSynchronizer 内部维护了一个双向链表
Node head:
Node tail;
int state;
Thread thread;

线程在竞争锁资源时,通过CAS将state从0设置为1,即代表竞争锁资源成功

非公平锁 的lock方法

final void lock() {// 以CAS的方式,尝试将 state从0变成1if (compareAndSetState(0, 1))//证明修改state状态成功,也就代表获取锁资源成功//将当前线程设置到AQS中的 exclusiveOwnerThread,代表当前线程拿着锁资源(和后面的可重入锁有关)setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);
}

公平锁 的lock方法

final void lock() {acquire(1);
}

无论公平锁还是非公平锁都会调用 acquire方法

acquire

public final void acquire(int arg) {// tryAcquire 分为公平锁实现和非公平锁实现//公平锁操作: 如果state为0 再看有线程排队,我就去排队,如果是锁重入的操作(如果是自己拿着锁资源),直接获取锁//非公平锁:   如果state为0 如果为0,直接尝试CAS修改,如果是锁重入的操作(如果是自己拿着锁资源),直接获取锁if (!tryAcquire(arg) &&// addWaiter方法,在线程没有通过 tryAcquire 拿到锁资源时,需要将当前线程封装为Node对象,去AQS内部排队// acquireQueued方法: 查看当前线程是否在队列前面的,是就尝试获取锁资源,长时间没拿到锁,就将当前线程挂起acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();
}

tryAcquire

tryAcquire 方法是AQS提供的,内部并没有任何实现,需要继承

非公平锁实现
final boolean nonfairTryAcquire(int acquires) {// 拿到当前线程final Thread current = Thread.currentThread();// 拿到 AQS的state值int c = getState();// 如果为0 就代表当前没有线程占用锁资源if (c == 0) {//直接基于 CAS 的方式,尝试修改 state, 从0~1,如果成功就代表拿到锁资源if (compareAndSetState(0, acquires)) {// 将 exclusiveOwnerThread 设置为当前线程setExclusiveOwnerThread(current);return true;}}// 说明state不为0,表示当前lock被线程占用//判断占用锁资源的线程是不是当前线程else if (current == getExclusiveOwnerThread()) {// 锁重入// 对state +1int nextc = c + acquires;// 判断锁重入是否到最大值if (nextc < 0) // overflowthrow 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();//获取 stateint 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;}//返回false,去排队return false;
}

公平锁和非公平锁的 tryAcquire方法的唯一区别就是,当判断state为0之后
公平锁会先查看是否有线程正在排队,如果有,直接返回false,如果没有尝试CAS获取锁资源
非公平锁不管有没有线程排队,直接以CAS尝试获取锁资源
state不为0二者都会判断是不是锁重入操作

在线程执行 tryAcquire方法没有获取到锁资源后,会返回false,在配置上if中的操作,
会执行 && 后面的方法,而在 acquireQueued(addWaiter(Node.EXCLUSIVE),arg)的参数中执行了 addWaiter,
要将当前获取锁失败的线程封装成Node, 排到AQS队列中

addWater
private Node addWaiter(Node mode) {// 将当前线程封装成Node对象Node node = new Node(Thread.currentThread(), mode);// 获取到tail节点,predNode pred = tail;//如果 tail不为空if (pred != null) {// 将当前节点的 prev 只想 tailnode.prev = pred;// 为了避免并发问题,以 CAS 方式,将tail 指向当前线程if (compareAndSetTail(pred, node)) {// 将之前的tail 的next 指向当前节点pred.next = node;// 返回当前节点return node;}}//如果在队列为空,或者CAS失败,会执行 enq ,将当前node 排到队列末尾 enq(node);return node;
}
private Node enq(final Node node) {for (;;) {// 获取tail 节点Node t = tail;if (t == null) { // 如果对列为空,先初始化head节点,作为头if (compareAndSetHead(new Node()))tail = head;} else {// 队列肯定不为空,采用之前的逻辑,知道将node插入到末尾node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}

整体逻辑为,先初始化node节点,将当前线程传入,表示为互斥锁
尝试将当前Node插入到AQS队列末尾
队列为空,执行enq,先初始化一个Node作为 head,然后再插入当前Node
队列不为空,直接将Node插入到末尾,作为tail

acquireQueued

首先查看当前Node是否排在队列的第一个位置 (不算Head),
直接再次执行tryAcquire方法竞争锁资源,尝试将当前线程挂起,
最终排在有效节点后,将当前线程挂起
//队伍前面竞争锁资源,队伍非前面,挂起线程

final boolean acquireQueued(final Node node, int arg) {//锁资源竞争失败标识boolean failed = true;try {// 中断标识boolean interrupted = false;for (;;) {// predecessor就是获取当前节点的上一个节点final Node p = node.predecessor();// 如果当前节点的上一个节点是 head ,就执行 tryAcquire ,尝试获取锁资源if (p == head && tryAcquire(arg)) {// 竞争锁资源成功,进入业务代码// 因为当前线程已经拿到锁资源,将当前线程的Node置为head,并将Node中的prev和thread置为nullsetHead(node);//将之前的头节点的next位置置为null,让GC将之前的head回收掉p.next = null; // 将获取锁资源失败的标志位置为 falsefailed = false;// 返回中断标识,默认为falsereturn interrupted;}//尝试将当前线程挂起//先判断能不能挂起,不能挂起,尝试让他能挂起if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {//在lock时不会触发if (failed)cancelAcquire(node);}
}

//判断当前线程是否可以挂起
static final int CANCELLED = 1; //绕过这个节点
static final int SIGNAL = -1; //标识节点可以被挂起
static final int CONDITION = -2; //将状态转为SIGNAL,然后挂起
static final int PROPAGATE = -3; //共享锁

shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {//拿到了上一个节点的状态int ws = pred.waitStatus;//如果 ws 为 -发直接返回true,当前节点可以挂起线程if (ws == Node.SIGNAL)return true;//如果 ws > 0 说明是 CANCELLED状态,绕过这个节点,找上一个节点的上一个if (ws > 0) {// 循环,直到找到上一个节点为小于等于0的节点do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0);pred.next = node;} else {// 可能为 0,-2,-3 直接以 CAS 的方式将节点状态改为 -1compareAndSetWaitStatus(pred, ws, Node.SIGNAL);}return false;
}//尝试将当前线程挂起,用 LockSupport
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
}

unlock方法

释放锁不分公平和非公平,都是执行sync的release方法
释放锁的核心,就是将state从大于0的数值更改为0,即为释放锁成功
并且unlock 方法应该涉及到将 AQS队列中阻塞的线程唤醒,

public void unlock() {sync.release(1);//只释放1
}

release

在释放锁时,只有state被减为0之后,才回去唤醒AQS队列中被挂起的线程,
在唤醒挂起线程时,如果head的next状态不正确,会从后往前找到离head最近的节点进行唤醒,
为什么从后往前找?
addWaiter方法是先将prev 指针赋值,最后才会将上一个节点的next指针赋值,
为了避免丢失节点或者跳过节点,必须从后往前找

public final boolean release(int arg) {//直接尝试去释放锁if (tryRelease(arg)) {// 释放锁成功Node h = head;// 如果head不是null,并且当前head的状态不为0if (h != null && h.waitStatus != 0)//说明 AQS的队列中有Node在排队,并且线程已经挂起了,需要唤醒它unparkSuccessor(h);return true;}return false;
}

tryRelease

//尝试释放锁

protected final boolean tryRelease(int releases) {//直接获取 state , 将state -1int c = getState() - releases;// 如果释放锁的线程,不是占用锁的线程,直接抛出异常if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;//判断 state 是否为0if (c == 0) {//锁释放成功free = true;// 将占用互斥锁的线程标志位置为 nullsetExclusiveOwnerThread(null);}// 锁重入了,一次没释放掉,将c赋值给state,等待下次释放setState(c);return free;
}

unparkSuccessor

//唤醒被挂起的线程

private void unparkSuccessor(Node node) {//获取head的状态int ws = node.waitStatus;if (ws < 0)将当前head的状态置为0compareAndSetWaitStatus(node, ws, 0);//获取下一个节点Node s = node.next;//如果下一个节点为null,或者状态为 CANCEL ,需要找到离head最近的有效节点if (s == null || s.waitStatus > 0) {s = null;//从后往前找这个节点for (Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}//找到最近的Node之后,唤醒它if (s != null)LockSupport.unpark(s.thread);
}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...