AbstractQueuedSynchronizer之AQS

目录

1.前置知识

2.AQS入门级别理论知识

2.1AQS是什么

 2.2AQS为什么是JUC内容中最重要的基石

2.3能干什么

3.AQS源码分析前置知识储备

 3.1AQS自身

3.2内部类Node(Node类在AQS类内部)

4.AQS源码深度理解和分析


1.前置知识

        公平锁和非公平锁、可重入锁、自旋思想、LockSupport、数据结构与双向链表、设计模式之模板方法模式

2.AQS入门级别理论知识

2.1AQS是什么

AbstractQueuedSynchronizer简称AQS,字面意思:抽象的队列同步器

技术解释 

是用来实现锁或者其它同步器组件的公共基础部分的抽象实现,是重量级基础框架及整个JUC体系的基石,主要用于解决锁分配给”谁”的问题。

整体就是一个抽象的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量表示持有锁的状态。

 2.2AQS为什么是JUC内容中最重要的基石

和AQS有关的类

ReentrantLock、CountDownLatch、ReentrantReadWriteLock、Semaphore等,里面关联有AQS的实现类。

进一步理解锁和同步器的关系

锁,面向锁的使用者:定义了程序员和锁交互的使用层API,隐藏了实现细节。

同步器,面向锁的实现者:Java并发大神DougLee,提出统一规范并简化了锁的实现,将其抽象出来,屏蔽了同步状态管理、同步队列的管理和维护、阻塞线程排队和通知、唤醒机制等,是一切锁和同步组件实现的--------公共基础部分。

2.3能干什么

加锁会导致阻塞,有阻塞就需要排队,实现排队必然需要队列。

解释说明

抢到资源的线程直接使用处理业务,抢不到资源的必然涉及一种排队等候机制。抢占资源失败的线程继续去等待(类似银行业务办理窗口都满了,暂时没有受理窗口的顾客只能去候客区排队等候),但等候线程仍然保留获取锁的可能且获取锁流程仍在继续(候客区的顾客也在等着叫号,轮到了再去受理窗口办理业务)。

既然说到了排队等候机制,那么就一定会有某种队列形成,这样的队列是什么数据结构呢?

如果共享资源被占用,就需要一定的阻塞等待唤醒机制来保证锁分配。这个机制主要用的是CLH队列的变体实现的,将暂时获取不到锁的线程加入到队列中,这个队列就是AQS同步队列的抽象表现。它将要请求共享资源的线程及自身的等待状态封装成队列的结点对象(Node),通过CAS、自旋以及LockSupport.park()的方式,维护state变量的状态,使并发达到同步的效果。

源码说明

AQS同步队列的基本结构

3.AQS源码分析前置知识储备

AQS内部体系构架

 3.1AQS自身

AQS的int变量

//AQS的同步状态State成员变量
private volatile int state;

AQS的CLH队列

CLH队列(三个大牛的名字组成),为一个双向队列

 小总结

有阻塞就需要排队,实现排队必然需要队列,state变量+CLH双向队列。

3.2内部类Node(Node类在AQS类内部)

Node的int变量

//Node的等待状态waitState成员变量

volatile int waitState

说人话

等候区其他顾客(其他线程)的等待状态,队列中每个排队的个体就是一个Node

Node的讲解

内部结构

属性说明

4.AQS源码深度理解和分析

Lock接口的实现类,基本都是通过【聚合】了一个【队列同步器】的子类完成线程访问控制的。

ReentrantLock的原理

从最简单的lock方法开始看看公平和非公平

以非公平锁ReentrantLock()为例作为突破走起,方法lock()

对比公平锁和非公平锁的tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断 IhasQueuedPredecessors()

hasQueuedPredecessors()中判断了是否需要排队,导致公平锁和非公平锁的差异如下:

公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;

非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程苏醒后,不一定就是排头的这个线程获得锁,它还是需要参加竞争锁(存在线程竞争的情况下),后来的线程可能不讲武德插队夺锁了。

 lock方法

Acquire方法

 tryAcquire(arg)方法

 return false,继续推进条件,走下一个方法;return true,结束。

addWaiter(Node.EXCLUSIVE)

 双向链表中,第一个节点为虚节点(也叫哨兵节点),其实并不存储任何信息,只是占位.真正的第一个有数据的节点,是从第二个节点开始的。

acquireQueued(addwaiter(Node.EXCLUSIVE), arg)

如果前驱节点的 waitStatus是SIGNAL状态,即shouldParkAfterFailedAcquire方法会返回true

程序会继续向下执行parkAndCheckInterrupt方法,用于将当前线程挂起 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Semaphore是一个信号量,它是基于AbstractQueuedSynchronizer (AQS)实现的。Semaphore的主要功能是控制同时访问某个资源的线程数量。通过Semaphore,我们可以实现对资源的限流,只允许一定数量的线程同时访问该资源。Semaphore中有一个计数器,表示当前可用的许可数。当一个线程请求许可时,如果计数器大于0,则线程可以获得许可并继续执行;如果计数器等于0,则线程被阻塞并放入等待队列中。当一个线程释放了许可时,计数器会增加,并通知等待队列中的一个线程可以继续执行。 AQS是一个抽象的队列同步器,它是Semaphore的实现基础。AQS中包含了一个双向链表,用于存储等待许可的线程。在获取许可时,线程会尝试获取许可,如果成功则可以继续执行,否则线程会被阻塞并加入等待队列中。在释放许可时,线程会释放持有的许可,并通知等待队列中的一个线程可以继续执行。 通过Semaphore和AQS,我们可以实现对资源的限流和线程的同步控制。Semaphore提供了acquire()方法用于获取许可,release()方法用于释放许可。在获取连接的时候,通过调用Semaphore的acquire()方法可以限制最多只能有一定数量的线程同时获取连接。在归还连接的时候,通过调用Semaphore的release()方法可以释放许可,使得等待队列中的线程可以获取连接继续执行。 综上所述,Semaphore和AQS是实现线程同步和资源限流的重要工具。Semaphore通过AQS实现了对资源的访问控制,通过acquire()和release()方法实现了线程的阻塞和释放。这样可以确保在并发环境下,多个线程对共享资源的访问是有序和安全的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值