ReentrantLock到AQS

新手学习,若有不对,欢迎大佬 调教

ReentrantLock

我们经常用的 *ReentrantLock*是干什么的呢 我认为这是一个前台/门面(类似设计模式中的门面模式)根据我们的入参创建一个FairSync OR NonfairSyncsync 担任锁的lock()和release()。

    private final Sync sync;

    public ReentrantLock() {
sync = new NonfairSync();
} public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

那有人可能就问了啥是公平锁(FairSync)? 啥是非公平锁(NonfairSync)?

就拿商场试吃举例子,前者就是大家都好好排队,后者是新来的看试吃小样还有,直接拿走不参与排队,那显然后面的人就会饥饿 啊。那非公平锁有什么意义呢。想象一下,当商场人满为患了,你去排到试吃的后面都要挤过来,挤过去。显然你在全局上影响了商场的客流动,如果你直接去 偷袭!(马保国音) 显然在商场全局上来说是最优的。

加锁

AQS入队

因为FairSyncNonfairSync 差的不是很大, 我们就着重讲NonfairSync

那你说那我缺的这块FairSync谁给我补啊,想要就自己来拿( 指自己看源码) 维吉尔音


//java.util.concurrent.locks.ReentrantLock
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L; final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

可见如果CAS成功线程就直接获得锁了,不成功就走了 acquire() 因为Sync extends AbstractQueuedSynchronizer让我们来看看acquire()

// java.util.concurrent.locks.AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

tryAcquire() 获取锁失败进入AQS等待队列

AQS终于是露出鸡脚了acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

AQS(AbstractQueuedSynchronizer)抽象队列同步器,名字是不是很高大上,我们别管

就是商场老大爷、老大妈排队购物呢(先进先出的双向链表)。

让我们看看node具有的属性

    static final class Node {
// 共有锁?
static final Node SHARED = new Node();
// 独占锁?
static final Node EXCLUSIVE = null; // 线程被取消
static final int CANCELLED = 1;
// 线程处于激活态
static final int SIGNAL = -1;
// 线程在等待中
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;

让我们再看看addWaiter()

    private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 对AQS进行初始化再加入
return node;
}

enq() 对队列进行初始化,添加一个虚拟节点(避免空指针)

    private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

AQS出队

让我们回到 acquire()

    public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

买菜大妈也挺急的,要排队就会催前面快点,于是拍拍前面的人,说往前催一下。(少数情况)前面的人也很急,看着时间来不及烧菜了,就自暴自弃,直接离开了,空出了位置。

    final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
} // 外部中断,或线程取消等待
} finally {
if (failed)
cancelAcquire(node);
}
}

后面的人看到前面有空位,就往前走再催前面的人。看到前面的人已经在催前面的人,他就不催了,催玩之后自己就能待机了(干着急也没用)。

为什么会 看到前面的人已经在催前面的人 可能有两个节点被同时加入

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)// 前面的人已经在问了
return true;
if (ws > 0) { // 取消节点,空出位置,往前挪
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

解锁

我们来看看锁的释放队列队列为空则调用unparkSuccessor(h) ,为什么 waitState以等于0做标记,且看下文

    public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) // 检查AQS是否初始化,或队列是否为空
unparkSuccessor(h);
return true;
}
return false;
}

waitState等于0可简单看做,已经完成了他作为解锁信号的职责,同时这和 -1是不一样的,

-1 是未知的往前催(不知道前面好没好),0是肯定的说前面有一个空位,并且是head指针自发的,不会传递。

private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 重置 waitStatus为 0
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) // 如果你观察到了这段的奇怪之处,我也没办法解释,看了文章也看到不是很明白,就不误导人了。相关内容在 java.util.concurrent.locks.AbstractQueuedSynchronizer#cancelAcquire
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒下一个线程
}

队列被 unpark() 唤醒,队伍可以向前移动了

如果觉得有帮到你

点个赞再走呗baby


参考文章:

不可不说的Java“锁”事

从ReentrantLock的实现看AQS的原理及应用

天天用lock,不好奇他到底怎么工作的吗 —ReentrantLock 大白话的更多相关文章

  1. 一文带你看懂Java中的Lock锁底层AQS到底是如何实现的

    前言 相信大家对Java中的Lock锁应该不会陌生,比如ReentrantLock,锁主要是用来解决解决多线程运行访问共享资源时的线程安全问题.那你是不是很好奇,这些Lock锁api是如何实现的呢?本 ...

  2. DNS 到底怎么工作的? (How does dns work?)

    其实这个问题每次看的时候都觉得很明白,但是很久之后就忘记了,所以这次准备记录下来.深入到这个过程的各个细节之中,以后多看看. Step 1 请求缓存信息: 当你在开始访问一个 www.baidu.co ...

  3. CPU:网卡老哥,你到底怎么工作的?

    阿Q造访 我是一个网卡,居住在一个机箱内的主板上,负责整台计算机的网络通信,要是没有我,这里就成了一个信息孤岛了,那也太无聊了- 上个周末,服务器断电维护了,这是我难得的休息时间,我准备打个盹儿眯一会 ...

  4. C#基础之lock

    1.lock的本质 实现线程同步的第一种方式是我们经常使用的lock关键字,它将包围的语句块标记为临界区,这样一次只有一个线程进入临界区并执行代码.下面第一段的几行代码是关于lock关键字的使用方式, ...

  5. docker到底比LXC多了些什么

    看似docker主要的OS级虚拟化操作是借助LXC, AUFS只是锦上添花.那么肯定会有人好奇docker到底比LXC多了些什么.无意中发现 stackoverflow 上正好有人问这个问题, 回答者 ...

  6. 为什么Java有了synchronized之后还造了Lock锁这个轮子?

    众所周知,synchronized和Lock锁是java并发变成中两大利器,可以用来解决线程安全的问题.但是为什么Java有了synchronized之后还是提供了Lock接口这个api,难道仅仅只是 ...

  7. Lock的实现之ReentrantLock详解

    摘要 Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作: 虽然锁有很多实现,但是都依赖AbstractQueuedSynchroniz ...

  8. Java并发包中Lock的实现原理

    1. Lock 的简介及使用 Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制.本质上Lock仅仅是一个接口(位于源码包中的java\util\concurrent\l ...

  9. java并发:线程同步机制之Lock

    一.初识Lock Lock是一个接口,提供了无条件的.可轮询的.定时的.可中断的锁获取操作,所有加锁和解锁的方法都是显式的,其包路径是:java.util.concurrent.locks.Lock, ...

  10. 5.Lock接口及其实现ReentrantLock

    jdk1.7.0_79 在java.util.concurrent.locks这个包中定义了和synchronized不一样的锁,重入锁——ReentrantLock,读写锁——ReadWriteLo ...

随机推荐

  1. MySurvey 问卷调查, 一个简单的Biwen.QuickApi示例项目

    MySurvey 项目 很久没更新我的博客了,之前开发的Biwen.QuickApi微框架 一直没有开发一个示例项目,最近有点时间,写了一个示例项目稍微介绍下 项目简介 这是一个基于 Biwen.Qu ...

  2. Sa-Token v1.42.0 发布 🚀,新增 API Key、TOTP 验证码、RefreshToken 反查等能力

    Sa-Token 是一款 免费.开源 的轻量级 Java 权限认证框架,主要解决:登录认证.权限认证.单点登录.OAuth2.0.微服务网关鉴权 等一系列权限相关问题. 目前最新版本 v1.42.0 ...

  3. ArrayBlockingQueue的take()底层原理

    一.ArrayBlockingQueue 的 take() 方法的底层源码的详细介绍 ArrayBlockingQueue 是 Java 并发包 (java.util.concurrent) 中的一个 ...

  4. 如何在 Go 中解析 yaml 文件

    Go 语言没有内置解析 yaml 文件的功能,实现 yaml 的解析可以使用第三方库 gopkg.in/yaml.v2 和 gopkg.in/yaml.v3. 下面以解析 config.yml 文件为 ...

  5. 《Beating Floating Point at its Own Game: Posit Arithmetic》(一)

    这段话描述了 Posit数制 在 动态扩展位数(追加比特) 时如何插入新的数值,并保持数值在数轴(或"数环")上的分布特性.以下是逐条解析: 1. Posit数值的动态扩展规则 P ...

  6. Asp.net core 少走弯路系列教程(二)HTML 学习

    前言 新人学习成本很高,网络上太多的名词和框架,全部学习会浪费大量的时间和精力. 新手缺乏学习内容的辨别能力,本系列文章为新手过滤掉不适合的学习内容(比如多线程等等),让新手少走弯路直通罗马. 作者认 ...

  7. Fiddler的安装和使用教程(详细)

    一.安装 1.fiddler工具下载网址:http://www.telerik.com/download/fiddler. 2.运行 FiddlerSetup.exe一键完成安装. 3.安装成功后点击 ...

  8. servlet web.xml配置

    url-pattern定义匹配规则,取值说明: 精确匹配 /具体的名称 只要url路径是集体的名称的时候才会触发Servlet 后缀匹配 *.xxx 只要是以xxx结尾的就匹配触发Servlet 通配 ...

  9. 【ROS】1.1 ROS基本命令介绍

    原视频 ROS基本命令 右键新标签页查看大图! have to do Command Command Result 中文解释 图示 roscore Open the core of the ROS. ...

  10. LinqHelper拓展

    public static class LinqHelper { //NHibernate.Hql.Ast.HqlBooleanExpression public static Expression& ...