AQS源码探究---竞争锁资源

我们进入ReentrantLock源码中查看其内部类

  • Sync 对AQS进行扩展公共方法并定义抽象方法的抽象类
  • FaireSync 实现公平锁的AQS的实现类
  • UnFairSync 实现非公平锁的ASQ的实现类

我使用例子进行的debug,然后一步一步看源码。例子在文章最后面

以下流程皆以非公平锁为例

线程竞争锁资源

AQS的state解释:

  • 0 表示锁没有被占用
  • 1 表示锁被占用了
  • > 1 表示锁被重入了 PS: ReentrantLock是可重入锁

获得锁执行流程

  1. 创建ReentrantLock对象
// ReetrantLock 默认创建一个非公平锁的AQS
public ReentrantLock() {
sync = new NonfairSync();
}
  1. 然后我们调用lock方法请求锁

    • 成功,即将锁的owner主人设置为当前线程,接下来就是回到线程中执行线程的任务。
    • 失败,即进入acquire的流程。
static final class NonfairSync extends Sync {

    final void lock() {
// 请求锁资源,如果将锁的state状态0改成1,即为成功获得锁资源
if (compareAndSetState(0, 1))
// 将锁的拥有者设置为当前线程,里面就一句话没啥好看的
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} }

下面是AQS阻塞链表是由一个双向链表组成的。

阻塞链表的成员对象Node的waitState状态解释:

  • CANCELLED = 1 表示线程已经被取消了
  • SIGNAL = -1 表示后继线程需要unpark解除阻塞,下图即表示。

锁竞争失败流程

  1. 进入acquire方法
public final void acquire(int arg) {
// 首先再次请求锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
  1. 首先会执行tryAcquire方法
protected final boolean tryAcquire(int acquires) { // 注意:我们进入的是非公平锁的tryAcquire实现
return nonfairTryAcquire(acquires);
}

再次进入nonfairTryAcquire(acquires)方法

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获得当前线程
int c = getState(); // 获得当前线程的状态
if (c == 0) { // 如果状态为0即锁资源被释放现在处于空闲状态,会尝试获得锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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; // 失败返回false。如果是成功获得锁或者是重入都会返回true。需要了解
}
  1. 回到步骤1代码,如果是失败返回false取反true,就会继续执行if语句。成功取反后false就直接结束当前语句,就会直接回到线程执行线程代码了。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 这里是两个方法,需要一个一个来
selfInterrupt();
}
// acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  1. 执行addWaiter方法,概括就是将没有获得锁的加入一个等待链表中。
private Node addWaiter(Node mode) {  // 刚创建的时候mode为null的
Node node = new Node(Thread.currentThread(), mode); // 首先创建一个node
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; // 将尾部的引用给pred变量
if (pred != null) { // 刚开始创建的时候pred是null的
node.prev = pred;
if (compareAndSetTail(pred, node)) { // 这个代码块就是cas尝试加入双向链表尾部
pred.next = node;
return node;
}
}
enq(node); // 这里是创建head和tail进的方法,和if (compareAndSetTail(pred, node))失败进入
return node; // 方法返回由当前线程创建的node
}

enq方法的进入条件

  • 进行head和tail的初始化。
  • 多线程下如果调用enq方法失败,就是当别的线程也进入了等待链表,此时tail就会改变,上面的cas就会false,没有返回,就会进行enq方法
private Node enq(final Node node) {
for (;;) {
Node t = tail; // 如果尾部为空就会进行初始化,没有的话不断进行cas尝试插入链表尾部。
if (t == null) { // Must initialize 初始化链表
if (compareAndSetHead(new Node())) // 我们可以看到head是指向一个没有参数的node对象的
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node; // 注意t还是引用旧值,而tail已经更新引用为node了。
return t;
}
}
}
}

疑问:

compareAndSetTail(t, node) 方法在我初次遇见的时候很奇怪。为什么t还算指向了旧的node对象

因为这个compareAndSetTail只是将tail的引用改变成了node,注意这边改变的是tail的引用。并没有去改变pred的引用。传入pred只是保证我们获得的尾部和现在的尾部是一样的,才能进行安全的尾部连接。

这也是我基础不太扎实的原因吧。

  1. 执行acquireQueued方法,再次尝试获得锁,和进行阻塞
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); // 获得node前驱
if (p == head && tryAcquire(arg)) { // 如果是第一个等待锁的线程,再次请求锁
setHead(node); // 请求成功就将该线程的node直接移出等待链表
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 检查状态并更新前驱状态为-1,即表示有后继节点阻塞了。
parkAndCheckInterrupt()) // 进入park,如果被中断返回true
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

在parkAndCheckInterrupt方法时进行park阻塞。

private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}

线程释放锁

  1. 调用unlock方法
public void unlock() {
sync.release(1);
}
  1. 调用release方法
public final boolean release(int arg) {
if (tryRelease(arg)) { // 进入tryRelease即尝试释放
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

进入tryRelease的ReentrantLock实现

protected final boolean tryRelease(int releases) {
int c = getState() - releases; // 获得当前的状态
if (Thread.currentThread() != getExclusiveOwnerThread()) // 非获得锁线程抛异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { // 如果没有重入直接释放锁将owner置为null
free = true;
setExclusiveOwnerThread(null);
}
setState(c); // 由于锁资源只有一个只有一个线程能更新状态,所以更新AQS状态不需要cas
return free;
}
  1. 继续回到release方法,释放锁成功返回true,进入条件语句
public final boolean release(int arg) {
if (tryRelease(arg)) { // 进入tryRelease即尝试释放
Node h = head;
if (h != null && h.waitStatus != 0) // 阻塞队列存在即头节点不为空且头节点的状态不为0,为0表示后面没节点阻塞了
unparkSuccessor(h);
return true;
}
return false;
}
  1. 进入unparkSuccessor方法,就不贴源码了,简单介绍一下就是将头节点置空,将阻塞队列中第一个等待的node解除阻塞,将他放出来去抢锁资源。

非公平锁和公平锁的区别

看完源码,整明白了就是锁资源释放后会放第一个等待线程去抢锁。

我就疑惑了,那明明就是公平的啊。

其实只是释放了线程,但是同时有其他的线程进行争抢,就又会变成争抢的情况,还是可能被其他线程抢走锁资源。

公平锁

就会判断如果阻塞链表是否为空,为空才能进行获取锁资源,又或者是锁重入

不然就是直接加入阻塞链表,从而实现了公平。

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

DEBUG例子

@Slf4j
public class Test1 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock();
// Reentrantlock锁资源被拥有
new Thread(()->{
lock.lock();
try{
log.debug("运行中");
try {
Thread.sleep(2000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
}).start();
// ReentrantLock阻塞链表初始化
new Thread(()->{
lock.lock();
try{
log.debug("运行中");
try {
Thread.sleep(2000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
}).start();
// ReentrantLock 再次向阻塞链表添加线程
new Thread(()->{
lock.lock();
try{
log.debug("运行中");
try {
Thread.sleep(2000000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
}).start();
} }

AQS源码探究之竞争锁资源的更多相关文章

  1. 全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(二)资源的获取和释放

    上期的<全网最详细的AbstractQueuedSynchronizer(AQS)源码剖析(一)AQS基础>中介绍了什么是AQS,以及AQS的基本结构.有了这些概念做铺垫之后,我们就可以正 ...

  2. 硬核剖析Java锁底层AQS源码,深入理解底层架构设计

    我们常见的并发锁ReentrantLock.CountDownLatch.Semaphore.CyclicBarrier都是基于AQS实现的,所以说不懂AQS实现原理的,就不能说了解Java锁. 上篇 ...

  3. AQS源码深入分析之独占模式-ReentrantLock锁特性详解

    本文基于JDK-8u261源码分析 相信大部分人知道AQS是因为ReentrantLock,ReentrantLock的底层是使用AQS来实现的.还有一部分人知道共享锁(Semaphore/Count ...

  4. ReentrantLock 与 AQS 源码分析

    ReentrantLock 与 AQS 源码分析 1. 基本结构    重入锁 ReetrantLock,JDK 1.5新增的类,作用与synchronized关键字相当,但比synchronized ...

  5. CyclicBarrier源码探究 (JDK 1.8)

    CyclicBarrier也叫回环栅栏,能够实现让一组线程运行到栅栏处并阻塞,等到所有线程都到达栅栏时再一起执行的功能."回环"意味着CyclicBarrier可以多次重复使用,相 ...

  6. AQS源码详细解读

    AQS源码详细解读 目录 AQS源码详细解读 基础 CAS相关知识 通过标识位进行线程挂起的并发编程范式 MPSC队列的实现技巧 代码讲解 独占模式 独占模式下请求资源 独占模式下的释放资源 共享模式 ...

  7. AQS源码深入分析之共享模式-你知道为什么AQS中要有PROPAGATE这个状态吗?

    本文基于JDK-8u261源码分析 本篇文章为AQS系列文的第二篇,前文请看:[传送门] 第一篇:AQS源码深入分析之独占模式-ReentrantLock锁特性详解 1 Semaphore概览 共享模 ...

  8. AQS源码深入分析之条件队列-你知道Java中的阻塞队列是如何实现的吗?

    本文基于JDK-8u261源码分析 1 简介 因为CLH队列中的线程,什么线程获取到锁,什么线程进入队列排队,什么线程释放锁,这些都是不受我们控制的.所以条件队列的出现为我们提供了主动式地.只有满足指 ...

  9. 深度分析ReentrantLock源码及AQS源码,从入门到入坟,建议先收藏!

    一.ReentrantLock与AQS简介 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:Reentra ...

随机推荐

  1. 学习Solr(二)

    一.Solr概述 1.什么是Solr Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器.Solr提供了比Lucene更为丰富的查询语言,同时实现了可 ...

  2. 深入 x64

      本篇原文为 X64 Deep Dive,如果有良好的英文基础的能力,可以点击该链接进行阅读.本文为我个人:寂静的羽夏(wingsummer) 中文翻译,非机翻,著作权归原作者所有.   由于原文十 ...

  3. Spark学习摘记 —— RDD行动操作API归纳

    本文参考 参考<Spark快速大数据分析>动物书中的第三章"RDD编程",前一篇文章已经概述了转化操作相关的API,本文再介绍行动操作API 和转化操作API不同的是, ...

  4. BMZCTF 端午节就该吃粽子

    端午节就该吃粽子 题目如下让我们访问login.php 然后就一个登录界面查看源码发现index.php 我们直接访问发现没有结果使用伪协议读取 然后我们使用base64解密 <?php err ...

  5. hitcon_2017_ssrfme

    hitcon_2017_ssrfme 进入环境给出源码 <?php if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $http_x_headers ...

  6. git 泄露(Log、Stash、Index)svn泄露

    Git泄露 Log 首先介绍下.git:Git泄露:当前大量开发人员使用git进行版本控制,对站点自动部署.如果配置不当,可能会将.git文件夹直接部署到线上环境.这就引起了git泄露漏洞. 首先使用 ...

  7. Apollo模块文章

    Apollo规划模块 自动驾驶公开课 | Apollo 2.5自动驾驶规划控制 : 这篇资料比较早,但是把EM Planner和Lattice Planner这两种在资料上经常看到的算法的来历和大概原 ...

  8. Unknown host mirrors.opencas.cn You may need to adjust the proxy settings in Gradle 报错及解决办法

    亲测Unknown host mirrors.opencas.cn You may need to adjust the proxy settings in Gradle 解决办法 - 程序员大本营 ...

  9. Input框搜索关键字高亮显示

    ruleTitle(text, val) { if (!val) return text; const result = text.replace( new RegExp(val, "g&q ...

  10. Python入门-常用模块

    1.sys,os import sys import os #获取当前的路径 print(sys.path[0]) print(os.getcwd()) print(os.path.abspath(& ...