程序地带

ReentrantLock!真正的公平锁和非公平锁!


ReentrantLock源码分析


文章目录
ReentrantLock源码分析公平锁非公平锁公平锁的实现Lock()方法unlock()方法
非公平锁的实现lock()方法unlock()方法
在这里插入图片描述我们都知道ReentrantLock是可重入的锁。这是他的类结构,其中分了三个内部类,Sync继承AbstractQueuedSynchronizer,NonfairSync和FairSync继承了Sync,顾名思义,NonfairSync是非公平锁,FairSync是公平锁。Sync锁的实现通过判断stat的值以及LockSupport类来实现阻塞。


公平锁

公平锁意思就是公平的。当有线程来请求锁的时候,必须遵循先请求先拿到锁的规则。即通过先入先出的队列来实现


非公平锁

非公平锁意思就是非公平的。实际上,我们更倾向于非公平锁。 即不遵循队列中的先进先出的规则,而是在队列中谁先抢到线程的执行权,或者说,锁。谁先出队列(这边卖个关子,实际上在源码中,在运行的时候并不是这样的。读者可以暂且认为是这样的,后面会解释!)


在ReentrantLock中默认的策略是非公平的。 可以通过ReentrantLock的构造方法设置它的公平性


public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁的实现
Lock()方法
@ReservedStackAccess
final void lock() {
if (!initialTryLock())
acquire(1);//如果失败那么再次获取
}
//NonfairSync中的initialTryLock
final boolean initialTryLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedThreads() && compareAndSetState(0, 1)) {//如果没有线程正在等待获取,那么设置stat为1
setExclusiveOwnerThread(current);//设置当前线程为独占线程
return true;
}
} else if (getExclusiveOwnerThread() == current) {//如果当前线程为独占的那个线程,那么stat++,重入
if (++c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
}
return false;
}
//查询是否有线程正在等待获取
public final boolean hasQueuedThreads() {
for (Node p = tail, h = head; p != h && p != null; p = p.prev)
if (p.status >= 0)
return true;
return false;
}
//acquire是aqs中的方法
public final void acquire(int arg) {
if (!tryAcquire(arg))//尝试请求锁
acquire(null, arg, false, false, false, 0L);//失败则入aqs队列
}
//ReentrantLock中FairSync中的方法
protected final boolean tryAcquire(int acquires) {
//公平的实现,如果当前锁没有人获取,那么再判断队列中有没有节点了,弱国没有那么,就设置当前线程执行,为独占线程
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
unlock()方法
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放锁
signalNext(head);//成果则唤醒下一个线程
return true;
}
return false;
}
//尝试释放
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//减少重入次数
if (getExclusiveOwnerThread() != Thread.currentThread())//如果当前线程不是独占线程,那么抛出异常
throw new IllegalMonitorStateException();
boolean free = (c == 0);
if (free)//如果为0,那么没有线程独占了
setExclusiveOwnerThread(null);
setState(c);
return free;
}
//解锁下一个节点的等待状态
private static void signalNext(Node h) {
Node s;
if (h != null && (s = h.next) != null && s.status != 0) {
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);//LockSupport解锁当前线程,waiter就是线程
}
}

ReentrantLock中除了Acquire方法中有for的死循环外,就不再有循环了。 因为Reentrant的公平锁是按照顺序来的,不存在争抢的。故和CountdownLatch不一样,CountdownLatch的线程执行是随机的,是争抢的,因此需要大量的循环cas。


非公平锁的实现
lock()方法
//lock方法用的是他们父类Sync的lock方法,和公平锁是一样的
public void lock() {
sync.lock();
}
@ReservedStackAccess
final void lock() {
if (!initialTryLock())//初始尝试锁不一样
acquire(1);
}
=================================================================================
//和公平锁十分相似的方法
final boolean initialTryLock() {
Thread current = Thread.currentThread();
//但是在if上不一样,公平锁还要判断是否有线程正在等待获取。而非公平锁是争抢的,因此不用判断
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
} else if (getExclusiveOwnerThread() == current) {
int c = getState() + 1;//同理,重入
if (c < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(c);
return true;
} else
return false;
}
=================================================================================
//acquire方法任然是aqs中的方法,和公平锁是一样的
public final void acquire(int arg) {
if (!tryAcquire(arg))//和公平锁的不一样,失败说明有其他线程已经获得了锁,那么当前线程进入aqs队列进行自旋等待
acquire(null, arg, false, false, false, 0L);
}
//同开始的tryInitalLock一样,少了判断是否有前驱的节点,而是直接设置值
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;//如果失败,则表明已经有其他的线程获得了锁,那么当前线程则放弃锁
}
unlock()方法
//unlock方法用的任然是sync中的release方法
public void unlock() {
sync.release(1);
}
//步骤任然是尝试释放,然后唤醒线程.
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head);
return true;
}
return false;
}
//tryRelease方法仍然是相同的
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (getExclusiveOwnerThread() != Thread.currentThread())
throw new IllegalMonitorStateException();
boolean free = (c == 0);
if (free)
setExclusiveOwnerThread(null);
setState(c);
return free;
}
//这是acuire方法,可以看到,其实
final int acquire(Node node, int arg, boolean shared,
boolean interruptible, boolean timed, long time) {
Thread current = Thread.currentThread();
byte spins = 0, postSpins = 0;
boolean interrupted = false, first = false;
Node pred = null;
for (;;) {
if (!first && (pred = (node == null) ? null : node.prev) != null &&
!(first = (head == pred))) {
if (pred.status < 0) {
cleanQueue();
continue;
} else if (pred.prev == null) {
Thread.onSpinWait();
continue;
}
}
if (first || pred == null) { //第一个for循环入~~~~~~~~~~~~~~
boolean acquired;
try {
if (shared)
acquired = (tryAcquireShared(arg) >= 0);
else
acquired = tryAcquire(arg);
} catch (Throwable ex) {
cancelAcquire(node, interrupted, false);
throw ex;
}
if (acquired) {
if (first) {
node.prev = null;
head = node;
pred.next = null;
node.waiter = null;
if (shared)
signalNextIfShared(node);
if (interrupted)
current.interrupt();
}
return 1;
}
}
if (node == null) {
if (shared)
node = new SharedNode();
else
node = new ExclusiveNode();//初始化独占node
} else if (pred == null) { //第二次tryacquire仍然没有拿到锁的情况
node.waiter = current;
Node t = tail;
node.setPrevRelaxed(t);
if (t == null)
tryInitializeHead();
else if (!casTail(t, node))
node.setPrevRelaxed(null);
else
t.next = node;
} else if (first && spins != 0) {
--spins;
Thread.onSpinWait();
} else if (node.status == 0) {//第三次for循环入口,还是没获取到锁
node.status = WAITING;
} else {
long nanos;
spins = postSpins = (byte)((postSpins << 1) | 1);
if (!timed)//第四次for循环还是没有获取到锁,那么直接调用LockSupport挂起线程
LockSupport.park(this);
else if ((nanos = time - System.nanoTime()) > 0L)
LockSupport.parkNanos(this, nanos);
else
break;
node.clearStatus();
if ((interrupted |= Thread.interrupted()) && interruptible)
break;
}
}
return cancelAcquire(node, interrupted, interruptible);
}

**注意!!!! 这个acquire是核心,是重点!!!!相当重要(从代码的长度就可以看出)


经过一段时间的debug之后,大概弄懂了这个acquire的方法的执行顺序。


第一次for循环,node为空,pred为空,执行tryacquire,如果获取锁成功,执行线程,后直接返回。若不成功进入步骤2初始化一个独占的node,然后进行第二次for循环第二次for循环,如果还是没有获取到锁,那么进入aqs队列,如果进入队列不成功(别的线程进入了队列)那么重复for循环进入队列第三次for循环,还是没有获取到锁,那么将当前node的status设为1,也就是waiting状态第四次for循环还是没有获取到锁,那么直接调用LockSupport挂起线程,等待执行完成的线程来unpark该线程。

在这些循环中,一旦有一次获取到了锁,那么会直接返回!!!


当线程结束后调用unpark激活队列中下一个线程


//unpark方法就在这个release方法中的signalnext方法中
public final boolean release(int arg) {
if (tryRelease(arg)) {
signalNext(head);//重点重点重点!!!!!!!!!!!!!!重要的事情要重复
return true;
}
return false;
}
//激活队列中当前线程的下一个线程
private static void signalNext(Node h) {
Node s;
if (h != null && (s = h.next) != null && s.status != 0) {
//条件是下一个线程不为空,并且status不为0,即为waiting状态
//如果不是waiting状态说明这个线程还在for循环的自旋中,并没有被LockSupport给挂起
s.getAndUnsetStatus(WAITING);
LockSupport.unpark(s.waiter);
}
}

我们都知道acquire是重点,但我要解释以下为什么说signalNext也是重点:


我们都知道非公平锁是的任意线程可以抢夺锁,但是为什么当前线程释放锁之后,激活的是下一个线程呢??? 这样不就是公平锁了吗????那这两者又有什么区别???


这个问题问的特别好! 为此我特地把这个问题的答案用代码块框出


/**
在所有线程被LockSupport挂起之后,那么不论是公平锁,还是非公平锁,都是默认唤醒队列中当前线程的下一个线程
然而,非公平锁的非公平体现在,tryAcquire()方法上!!!
我们再回头看一下fairSync和unfairSync的tryAcquire方法
核心点来了:
·······························································
公平锁,若队列不为空,没入队的线程不得抢锁,
非公平锁,若队列不为空,没入队的线程却可以抢锁,
这时后到的线程可能先抢到锁,即不公平。
·······························································
读者可能还会有点迷惑,那是没入队的不可以抢锁,没入队的可以抢锁
我们再仔细体会这两个方法。
·················
如果是非公平锁,记还没进aqs队列的线程为A,在运行的线程为B,队列中B的next记为B.next。
那么这个时候A和B.next是可以同时竞争这个锁的。
如果是非公平锁,记还没进aqs队列的线程为A,在运行的线程为B,队列中的next记为B.next。
那么这个时候tryAcquire的时候,因为有队列中还有节点,那么这个tryAcquire方法一定是false。
因此这个A线程一定会进入aqs队列然后不断的for循环tryAcquire,如果队列中就剩它这个A节点了,那么在tryAcuqire中
才会激活。或者在几次for循环后被LockSupport给挂起,等待他的前驱线程来唤醒它。
·················
*/
//这是公平锁
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && !hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//这是非公平锁
protected final boolean tryAcquire(int acquires) {
if (getState() == 0 && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}

在这里插入图片描述 最后给上图解 这就是真正的非公平锁,和公平锁。(回应上面卖的关子)!


说的不明白的地方恳请读者评论留言,我会进行修改


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46814474/article/details/111246180

随机推荐

阿里云备案不准挂谷歌广告

最近,站长“夏风”爆料,阿里云备案监管再次升级,官方要求所有阿里云备案的个人网站,没有ICP经营性备案手续,不允许在挂谷歌联盟广告...

酷爱码 阅读(706)

Java+gui+mysql实现学生管理系统

Java+gui+mysql实现学生管理系统​学完JavaSE和数据库部分就可以动手做一个简单的学生管理系统,其实也就是涉及到一些简单的数据库增删查改的内容,学艺不精项目中有些功能可能...

装14 阅读(783)

L298N电机驱动模块详解

L298N是专用驱动集成电路,属于H桥集成电路,与L293D的差别是起输出电流增大,功率增强。其输出电流为2A,最高电流4A,最高...

m0_46234896 阅读(927)

每天一个linux命令(21):find命令之xargs

在使用find命令的-exec选项处理匹配到的文件时,find命令将所有匹配到的文件一起传递给exec执行。但有些系统对能够传递给exec的命令长度有限制,这样在find命...

qq_2580123 阅读(475)

分布式架构的秒杀系统设计与实现

记录一下自己毕业设计作品的流程-基于分布式微服务架构的秒杀系统内存状态位:concurrentHashMap使用分布式锁的原因:防止秒缓存击穿异步多线程:同时...

尹会东 阅读(502)

golang get url传参_Golang之defer

defer关键字是Golang引入的特征关键字之一,方便开发者做开发,在某些场景真的是太方便了,可以少写很多重复的代码。比如在错误处理场景,如果...

大宝藏 阅读(314)

Vue手写tab选项卡,底部滑动动画

注意事项:点击li事件获取动态DOM的宽度和偏移值时需要写在nextTick方法里,否则需要点击两次!<template><divclass...

GHY_sheep 阅读(413)

Verilog经验总结

1.1verilog经验总结1.1.1本节目录1)本节目录;2)本节引言;3)verilog简介;4)verilog设计经验;5)结束语。1.1.2本节引言“不积跬步,无以至千里;不积小流,无以成江海...

宁静致远dream 阅读(709)