AbstractQueuedSynchronizer源码解析(上)(1)

最后

分享一些系统的面试题,大家可以拿去刷一刷,准备面试涨薪。

这些面试题相对应的技术点:

  • JVM
  • MySQL
  • Mybatis
  • MongoDB
  • Redis
  • Spring
  • Spring boot
  • Spring cloud
  • Kafka
  • RabbitMQ
  • Nginx

大类就是:

  • Java基础
  • 数据结构与算法
  • 并发编程
  • 数据库
  • 设计模式
  • 微服务
  • 消息中间件

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

程序员,每个月给你发多少工资,你才会想老板想的事?

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

2.1 类定义


AQS 类定义代码如下:

public abstract class AbstractQueuedSynchronizer

extends AbstractOwnableSynchronizer

implements java.io.Serializable {

可以看出两点:

  1. AQS 是个抽象类,就是给各种锁子类继承用的,AQS 定义了很多如何获得锁,如何释放锁的抽象方法,目的就是为了让子类去实现;

  2. 继承了 AbstractOwnableSynchronizer,AbstractOwnableSynchronizer 的作用就是为了知道当前是那个线程获得了锁,方便监控用的,代码如下:

在这里插入图片描述

2.2 基本属性


AQS 的属性可简单分为四类:同步器简单属性、同步队列属性、条件队列属性、公用 Node。

2.2.1 简单属性

让我们来看一下简单属性都有哪些:

最重要的就是 state 属性,是 int 类型的,所有继承 AQS 的锁都是通过这个字段来判断能不能获得锁,能不能释放锁。

// 同步器的状态,子类会根据状态字段进行判断是否可以获得锁

// 比如 CAS 成功给 state 赋值 1 算得到锁,赋值失败为得不到锁, CAS 成功给 state 赋值 0 算释放锁,赋值失败为释放失败

// 可重入锁,每次获得锁 +1,每次释放锁 -1

private volatile long state;

// 自旋超时阀值,单位纳秒

// 当设置等待时间时才会用到这个属性

static final long spinForTimeoutThreshold = 1000L;

2.1.2 共享锁和排它锁的区别

排它锁:同一时刻,只能有一个线程可以获得锁,也只能有一个线程可以释放锁。

共享锁:可以允许多个线程获得同一个锁,并且可以设置获取锁的线程数量。

2.1.3 同步队列属性

同步队列:当多个线程都来请求锁时,某一时刻有且只有一个线程能够获得锁(排它锁),那么剩余获取不到锁的线程,都会到同步队列中去排队并阻塞自己,当有线程主动释放锁时,就会从同步队列头开始释放一个排队的线程,让线程重新去竞争锁。

所以同步队列的主要作用阻塞获取不到锁的线程,并在适当时机释放这些线程。

同步队列底层数据结构是个双向链表

// 同步队列的头。

private transient volatile Node head;

// 同步队列的尾

private transient volatile Node tail;

源码中的 Node 是同步队列中的元素,但 Node 被同步队列和条件队列公用,我们在后面再说 Node。

2.1.4 条件队列的属性

条件队列和同步队列的功能一样,管理获取不到锁的线程,底层数据结构也是链表队列,但条件队列不直接和锁打交道,但常常和锁配合使用,是一定的场景下,对锁功能的一种补充。

条件队列的属性如下:

public class ConditionObject implements Condition, java.io.Serializable {

private static final long serialVersionUID = 1173984872572414699L;

/** First node of condition queue. */

// 条件队列中第一个 node

private transient Node firstWaiter;

/** Last node of condition queue. */

// 条件队列中最后一个 node

private transient Node lastWaiter;

//…//

}

ConditionObject 称为条件队列,我们需要使用时,直接 new ConditionObject () 即可。

2.1.5 Node

Node 非常重要,即是同步队列的节点,又是条件队列的节点,在入队的时候,我们用 Node 把线程包装一下,然后把 Node 放入两个队列中:

static final class Node {

/**

  • 同步队列单独的属性

*/

//node 指示节点在共享模式下等待的标记

static final Node SHARED = new Node();

//node 指示节点正在以独占模式等待的标记

static final Node EXCLUSIVE = null;

// 当前节点的前节点

// 节点获得成功后就会变成head

// head 节点不能被取消

volatile Node prev;

// 当前节点的下一个节点

volatile Node next;

/**

  • 两个队列共享的属性

*/

// 表示当前节点的状态,通过节点的状态来控制节点的行为

// 普通同步节点,就是 0 ,条件节点是 CONDITION -2

volatile int waitStatus;

/**

  • waitStatus 的状态有以下几种

*/

// 被取消

static final int CANCELLED = 1;

// SIGNAL 状态的意义:同步队列中的节点在自旋获取锁的时候

// 如果前一个节点的状态是 SIGNAL,那么自己就可以阻塞休息了,否则自己一直自旋尝试获得锁

static final int SIGNAL = -1;

// 表示当前 node 正在条件队列中

// 当有节点从同步队列转移到条件队列时,状态就会被更改成 CONDITION

static final int CONDITION = -2;

// 无条件传播,共享模式下,该状态的进程处于可运行状态

static final int PROPAGATE = -3;

// 当前节点的线程

volatile Thread thread;

// 在同步队列中,nextWaiter 并不真的是指向其下一个节点

// 我们用 next 表示同步队列的下一个节点,nextWaiter 只是表示当前 Node 是排它模式还是共享模式

// 但在条件队列中,nextWaiter 就是表示下一个节点元素

Node nextWaiter;

//…//

}

2.3 Condition


刚才我们看条件队列 ConditionObject 时,发现其是实现 Condition 接口的,现在我们一起来看下 Condition 接口,其类注释上是这么写的:

  1. 当 lock 代替 synchronized 来加锁时,Condition 就可以用来代替 Object 中相应的监控方法了,比如 Object wait ()、Object notify、Object notifyAll 这些方法;

  2. 提供了一种线程协作方式:一个线程被暂停执行,直到被其它线程唤醒;

  3. Condition 实例是绑定在锁上的,通过 Lock中的newCondition 方法可以产生该实例;

  4. 除了特殊说明外,任意空值作为方法的入参,都会抛出空指针;

  5. Condition 提供了明确的语义和行为,这点和 Object 监控方法不同。

类注释上甚至还给我们举了一个例子:

例如,假设我们有一个支持 put 和 take 方法的有界缓冲区。如果在空缓冲区上尝试获取,则线程将阻塞,直到项目可用;如果在一个完整的缓冲区上尝试放置,则线程将阻塞,直到有可用空间为止。我们希望继续等待 put 线程并将线程放在单独的等待集中,以便我们可以使用优化,当缓冲区中的项目或空间变得可用时,一次只通知单个线程。这可以使用两个 Condition 实例来实现。

假设我们有一个有界边界的队列,支持 put 和 take 方法,需要满足:

  1. 如果试图往空队列上执行 take,线程将会阻塞,直到队列中有可用的元素为止;

  2. 如果试图往满的队列上执行 put,线程将会阻塞,直到队列中有空闲的位置为止。

1、2 中线程阻塞都会到条件队列中去阻塞。

take 和 put 两种操作如果依靠一个条件队列,那么每次只能执行一种操作,所以我们可以新建两个条件队列,这样就可以分别执行操作了。

最后

金三银四马上就到了,希望大家能好好学习一下这些技术点

学习视频:

大厂面试真题:

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

塞都会到条件队列中去阻塞。

take 和 put 两种操作如果依靠一个条件队列,那么每次只能执行一种操作,所以我们可以新建两个条件队列,这样就可以分别执行操作了。

最后

金三银四马上就到了,希望大家能好好学习一下这些技术点

学习视频:

[外链图片转存中…(img-0UhNXoWG-1715804618920)]

大厂面试真题:

[外链图片转存中…(img-N5lbCPJy-1715804618920)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值